Setup
workflow_name <- "netnav_06_model_comparison"
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.4.4 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(here)
## here() starts at /Users/jaeyoungson/Documents/GitHub/network-navigation-replay
library(patchwork)
library(glmmTMB)
## Warning in checkDepPackageVersion(dep_pkg = "TMB"): Package version inconsistency detected.
## glmmTMB was built with TMB version 1.9.6
## Current TMB version is 1.9.10
## Please re-install glmmTMB from source or restore original 'TMB' package (see '?reinstalling' for more information)
library(broom.mixed)
source(here("code", "utils", "modeling_utils.R"))
source(here("code", "utils", "representation_utils.R"))
source(here("code", "utils", "bayesian_model_selection.R"))
source(here("code", "utils", "ggplot_themes.R"))
source(here("code", "utils", "kable_utils.R"))
##
## Attaching package: 'kableExtra'
##
## The following object is masked from 'package:dplyr':
##
## group_rows
source(here("code", "utils", "unicode_greek.R"))
knitting <- knitr::is_html_output()
create_path <- function(this_path) {
if (!dir.exists(this_path)) {
dir.create(this_path, recursive = TRUE)
}
}
predict_glmmTMB <- function(make_predictions_for, model_object) {
make_predictions_for %>%
bind_cols(
predict(
object = model_object,
newdata = .,
re.form = NA, allow.new.levels = TRUE, se.fit = TRUE, type = "response"
)
)
}
if (knitting) {
here("outputs", workflow_name) %>%
create_path()
here("figures") %>%
create_path()
}
nav_study1 <- here("data", "clean_data", "study1_message_passing.csv") %>%
read_csv(show_col_types = FALSE) %>%
filter(
# two_correct_options == FALSE,
shortest_path_given_opts == shortest_path_given_start_end
) %>%
mutate(
study = "Study 1",
measurement_id = str_c("D", measurement_id),
shortest_path = factor(shortest_path_given_opts)
) %>%
select(
study, sub_id, measurement_id, shortest_path,
startpoint_id, endpoint_id,
opt1_id, opt2_id,
correct_choice, sub_choice,
correct, rt,
two_correct_options,
opt1_distance = dist_opt1,
opt2_distance = dist_opt2
)
nav_study2 <- here("data", "clean_data", "study2_message_passing.csv") %>%
read_csv(show_col_types = FALSE) %>%
filter(
# two_correct_options == FALSE,
shortest_path_given_opts == shortest_path_given_start_end
) %>%
mutate(
study = "Study 2",
measurement_id = case_when(
network == "learned" ~ str_c("D", measurement_id),
network == "reevaluated" ~ "D2b"
),
shortest_path = factor(shortest_path_given_opts)
) %>%
select(
study, sub_id, measurement_id, shortest_path,
startpoint_id, endpoint_id,
opt1_id, opt2_id,
correct_choice, sub_choice,
correct, rt,
two_correct_options,
opt1_distance = dist_opt1,
opt2_distance = dist_opt2
)
nav_study3 <- here("data", "clean_data", "study3_message_passing.csv") %>%
read_csv(show_col_types = FALSE) %>%
filter(
# two_correct_options == FALSE,
shortest_path_given_opts == shortest_path_given_start_end
) %>%
mutate(
study = "Study 3",
measurement_id = case_when(
network == "reevaluated" ~ "D2b",
measurement_id == 1 ~ "D1",
measurement_id == 2 ~ "D1b",
measurement_id == 3 ~ "D2"
),
shortest_path = factor(shortest_path_given_opts)
) %>%
select(
study, sub_id, measurement_id, shortest_path,
startpoint_id, endpoint_id,
opt1_id, opt2_id,
correct_choice, sub_choice,
correct, rt,
two_correct_options,
opt1_distance = dist_opt1,
opt2_distance = dist_opt2
)
bfs_backward_sims <- here(
"data", "bfs_sims", "bfs_sims_learned_backward.csv"
) %>%
read_csv(show_col_types = FALSE) %>%
filter(
shortest_path_given_opts == shortest_path_given_start_end,
# two_correct_options == FALSE
) %>%
mutate(shortest_path = factor(shortest_path_given_opts)) %>%
select(-starts_with("shortest_path_given")) %>%
group_by(
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
correct_choice, two_correct_options
) %>%
summarise(
p_bfs_correct = mean(bfs_choice == correct_choice),
p_bfs_chooses_opt1 = mean(bfs_choice == opt1_id),
bfs_visits = mean(bfs_n_visits_total),
.groups = "drop"
)
bfs_forward_sims <- here("data", "bfs_sims", "bfs_sims_learned_forward.csv") %>%
read_csv(show_col_types = FALSE) %>%
filter(
shortest_path_given_opts == shortest_path_given_start_end,
# two_correct_options == FALSE
) %>%
mutate(shortest_path = factor(shortest_path_given_opts)) %>%
select(-starts_with("shortest_path_given")) %>%
group_by(
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
correct_choice, two_correct_options
) %>%
summarise(
p_bfs_correct = mean(bfs_choice == correct_choice),
p_bfs_chooses_opt1 = mean(bfs_choice == opt1_id),
bfs_visits = mean(bfs_n_visits_total),
.groups = "drop"
)
nav_trials <- here("data", "clean_data", "study1_message_passing.csv") %>%
read_csv(show_col_types = FALSE) %>%
filter(
# two_correct_options == FALSE,
shortest_path_given_opts == shortest_path_given_start_end
) %>%
mutate(shortest_path = factor(shortest_path_given_opts)) %>%
filter(sub_id == 1) %>%
select(
shortest_path,
startpoint_id, endpoint_id,
opt1_id, opt2_id,
correct_choice,
opt1_distance = dist_opt1,
opt2_distance = dist_opt2,
two_correct_options
) %>%
arrange(shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id) %>%
# Replace undefined distances (corresponding to impossible options)
# so that the softmax gets non-NA inputs; we assume that impossible
# options are just as bad as the longest distance found in this set
# of trials, i.e., a distance of 8
mutate(across(c(opt1_distance, opt2_distance), ~replace_na(.x, 8)))
adjlist <- here("data", "clean_data", "adjlist_learned.csv") %>%
read_csv(show_col_types = FALSE)
transmat <- adjlist %>%
group_by(from) %>%
mutate(edge = edge / sum(edge)) %>%
ungroup() %>%
pivot_wider(names_from = to, values_from = edge) %>%
column_to_rownames("from") %>%
as.matrix()
load_params_from_scratch <- FALSE
if (load_params_from_scratch == TRUE) {
params <- here("data", "param_fits") %>%
fs::dir_ls(
recurse = 1,
regexp = str_c(
"_",
"(bfs_(backward|forward)|",
"ideal_obs|",
"sr_(analytic|delta_rule))_",
"(.)+\\.csv"
)
) %>%
map_dfr(
.f = ~read_csv(.x, show_col_types = FALSE),
.id = "filename"
) %>%
mutate(
# Recover model ID
model = str_extract(
filename,
str_c(
"_",
"(bfs_(backward|forward)|",
"ideal_obs|",
"sr_(analytic|delta_rule))_"
)
),
model = str_sub(model, 2, -2),
# Recover study ID
study = str_extract(filename, "study[[:digit:]]"),
study = str_replace(study, "study", "Study "),
# Recover subject ID
sub_id = str_extract(filename, "sub_[[:digit:]]+"),
sub_id = str_remove(sub_id, "sub_"),
sub_id = as.integer(sub_id),
# Recover measurement ID
measurement_id = str_extract(filename, "_D[[:digit:]]b?"),
measurement_id = str_remove(measurement_id, "_"),
# Get parameter values
param_value = if_else(
is.na(param_value_human_readable),
param_value,
param_value_human_readable
)
) %>%
# Find best-fitting optimization run
filter(convergence == "converged") %>%
group_by(model, study, sub_id, measurement_id) %>%
slice_min(optim_value, n = 1) %>%
ungroup() %>%
# Some subjects may have had multiple "best" optimization runs
# In that case, just go with whichever "best" run was estimated first
group_by(model, study, sub_id, measurement_id) %>%
slice_min(optimizer_run, n = 1) %>%
ungroup() %>%
# Clean up
select(
model, study, sub_id, measurement_id,
param_name, param_value,
neg_loglik = optim_value
) %>%
arrange(model, study, sub_id, measurement_id, param_name)
here("data", "param_fits", "clean_params") %>%
create_path()
params %>%
write_csv(
file = here("data", "param_fits", "clean_params", "clean_param_fits.csv")
)
}
params <- here("data", "param_fits", "clean_params", "clean_param_fits.csv") %>%
read_csv(show_col_types = FALSE)
AICc
As our metric of log-evidence, we’ll use AICc, i.e., AIC corrected
for a relatively small N.
aicc <- params %>%
select(study, sub_id, measurement_id, model, neg_loglik) %>%
distinct() %>%
arrange(study, sub_id, measurement_id) %>%
mutate(
n_params = if_else(model == "sr", 2, 1),
n_datapoints = 115,
aic = (-2 * -neg_loglik) + (2 * n_params),
aicc = aic + (
(2 * n_params * (n_params + 1)) / (n_datapoints - n_params - 1)
)
)
SR analytic vs delta-rule
Before we go any further, let’s just get one targeted comparison out
of the way. In earlier scripts, we saw that SR matrices can be
constructed using a closed-form analytic solution, or a delta-rule
updating mechanism. These different implementations can, in principle,
end up making very different predictions. Here, we’ll see whether
there’s evidence that one implementation fits better than the other.
Below, we’re directly comparing the AICc of the analytic vs
delta-rule implementations. Each datapoint is one subject, and the lines
connect a subject’s AICc from one implementation to the other. The red
bars reflect the means. The lines are remarkably flat, indicating that
there is functionally no real difference in the model
goodness-of-fit.
plot_sr_comparison_aicc <- aicc %>%
filter(str_detect(model, "sr_")) %>%
mutate(facet_label = str_c(study, ", ", measurement_id)) %>%
ggplot(aes(x=model, y=aicc)) +
theme_custom() +
facet_wrap(~facet_label, scales = "free_x") +
geom_point(alpha = 0.5) +
geom_line(aes(group = sub_id), alpha = 0.2) +
stat_summary(geom = "crossbar", fun = mean, color = "red") +
scale_x_discrete(
name = "SR implementation",
labels = c("sr_analytic"="Analytic", "sr_delta_rule"="Delta-rule")
) +
ylab("AICc") +
ggtitle("AICc comparison of SR implementations")
plot_sr_comparison_aicc

if (knitting) {
ggsave(
filename = here(
"outputs", workflow_name,
"sr_aicc_analytic_vs_delta_rule.pdf"
),
plot = plot_sr_comparison_aicc,
width = 6, height = 4,
units = "in", dpi = 300
)
}
We’re not interested in doing any sort of hypothesis testing here,
but for the purpose of doing model selection, we do want to know whether
choosing one implementation over the other might result in forming
different conclusions about the parameter fits.
Below, the bars reflect medians. We can see that, by-and-large, the
two implementations result in very similar estimates.
plot_sr_comparison_gamma <- params %>%
filter(str_detect(model, "sr_")) %>%
filter(param_name == "sr_gamma") %>%
select(model, study, sub_id, measurement_id, sr_gamma = param_value) %>%
ggplot(aes(x=measurement_id, y=sr_gamma, color=model)) +
theme_custom() +
facet_wrap(~study, scales = "free_x") +
geom_point(
alpha = 0.5, position = position_dodge(width = 0.25), show.legend = FALSE
) +
geom_line(aes(group = interaction(model, sub_id)), alpha = 0.2) +
stat_summary(geom = "crossbar", fun = median) +
xlab("Measurement") +
ylab("SR gamma") +
scale_color_manual(
name = "SR implementation",
labels = c("sr_analytic"="Analytic", "sr_delta_rule"="Delta-rule"),
values = c("sr_analytic"="#ca0020", "sr_delta_rule"="#0571b0")
) +
ggtitle("Estimates of SR gamma: Analytic vs delta-rule") +
theme(legend.position = "bottom")
plot_sr_comparison_gamma
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?

if (knitting) {
ggsave(
filename = here(
"outputs", workflow_name,
"sr_gamma_analytic_vs_delta_rule.pdf"
),
plot = plot_sr_comparison_gamma,
width = 6, height = 4,
units = "in", dpi = 300
)
}
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
In the modeling, the goal was to test changes in the gamma parameter,
assuming an asymptotic representation. These results suggest that there
is nothing lost by using the analytic closed-form implementation, so
we’ll stick with that from here onwards.
Description of parameter fits
params %>%
group_by(model, study, measurement_id, param_name) %>%
summarise(
param_mean = mean(param_value),
param_median = median(param_value),
.groups = "drop"
) %>%
kable_custom(
captions = "Descriptive stats: parameter fits",
grouping_var = model
)
Descriptive stats: parameter fits
|
study
|
measurement_id
|
param_name
|
param_mean
|
param_median
|
|
bfs_backward
|
|
Study 1
|
D1
|
search_threshold
|
3.834
|
3.659
|
|
Study 2
|
D1
|
search_threshold
|
4.769
|
4.431
|
|
Study 2
|
D2
|
search_threshold
|
5.319
|
4.762
|
|
Study 3
|
D1
|
search_threshold
|
5.072
|
4.535
|
|
Study 3
|
D1b
|
search_threshold
|
4.870
|
4.144
|
|
Study 3
|
D2
|
search_threshold
|
5.763
|
4.652
|
|
bfs_forward
|
|
Study 1
|
D1
|
search_threshold
|
5.926
|
6.038
|
|
Study 2
|
D1
|
search_threshold
|
7.438
|
6.854
|
|
Study 2
|
D2
|
search_threshold
|
8.116
|
7.168
|
|
Study 3
|
D1
|
search_threshold
|
7.783
|
6.625
|
|
Study 3
|
D1b
|
search_threshold
|
7.611
|
6.606
|
|
Study 3
|
D2
|
search_threshold
|
8.618
|
7.404
|
|
ideal_obs
|
|
Study 1
|
D1
|
softmax_temperature
|
-0.272
|
-0.184
|
|
Study 2
|
D1
|
softmax_temperature
|
-0.475
|
-0.289
|
|
Study 2
|
D2
|
softmax_temperature
|
-0.685
|
-0.353
|
|
Study 3
|
D1
|
softmax_temperature
|
-0.687
|
-0.284
|
|
Study 3
|
D1b
|
softmax_temperature
|
-0.696
|
-0.319
|
|
Study 3
|
D2
|
softmax_temperature
|
-1.024
|
-0.380
|
|
sr_analytic
|
|
Study 1
|
D1
|
softmax_temperature
|
1797.497
|
26.023
|
|
Study 1
|
D1
|
sr_gamma
|
0.567
|
0.647
|
|
Study 2
|
D1
|
softmax_temperature
|
285.337
|
24.286
|
|
Study 2
|
D1
|
sr_gamma
|
0.659
|
0.722
|
|
Study 2
|
D2
|
softmax_temperature
|
560.735
|
30.060
|
|
Study 2
|
D2
|
sr_gamma
|
0.722
|
0.826
|
|
Study 3
|
D1
|
softmax_temperature
|
688.291
|
31.783
|
|
Study 3
|
D1
|
sr_gamma
|
0.690
|
0.816
|
|
Study 3
|
D1b
|
softmax_temperature
|
761.781
|
40.193
|
|
Study 3
|
D1b
|
sr_gamma
|
0.673
|
0.825
|
|
Study 3
|
D2
|
softmax_temperature
|
2041.139
|
54.912
|
|
Study 3
|
D2
|
sr_gamma
|
0.718
|
0.834
|
|
sr_delta_rule
|
|
Study 1
|
D1
|
softmax_temperature
|
45.047
|
12.202
|
|
Study 1
|
D1
|
sr_gamma
|
0.557
|
0.639
|
|
Study 2
|
D1
|
softmax_temperature
|
32.039
|
13.713
|
|
Study 2
|
D1
|
sr_gamma
|
0.649
|
0.709
|
|
Study 2
|
D2
|
softmax_temperature
|
119.106
|
21.976
|
|
Study 2
|
D2
|
sr_gamma
|
0.714
|
0.819
|
|
Study 3
|
D1
|
softmax_temperature
|
105.173
|
18.794
|
|
Study 3
|
D1
|
sr_gamma
|
0.680
|
0.790
|
|
Study 3
|
D1b
|
softmax_temperature
|
40.854
|
15.469
|
|
Study 3
|
D1b
|
sr_gamma
|
0.667
|
0.813
|
|
Study 3
|
D2
|
softmax_temperature
|
278.951
|
24.712
|
|
Study 3
|
D2
|
sr_gamma
|
0.716
|
0.835
|
plot_params_bfs_backward <- params %>%
filter(model == "bfs_backward") %>%
pivot_wider(names_from = param_name, values_from = param_value) %>%
ggplot(aes(x=measurement_id, y=search_threshold)) +
theme_custom() +
facet_wrap(~study, scales = "free_x") +
stat_summary(geom = "crossbar", fun = mean, color = "red") +
stat_summary(geom = "crossbar", fun = median, color = "blue") +
geom_point(
alpha = 0.1,
position = position_jitter(width = 0.1, height = 0, seed = 1)
) +
geom_line(
aes(group = sub_id), alpha = 0.25,
position = position_jitter(width = 0.1, height = 0, seed = 1)
) +
xlab("Measurement") +
ylab("Search threshold") +
ggtitle("BFS-backward")
plot_params_bfs_forward <- params %>%
filter(model == "bfs_forward") %>%
pivot_wider(names_from = param_name, values_from = param_value) %>%
ggplot(aes(x=measurement_id, y=search_threshold)) +
theme_custom() +
facet_wrap(~study, scales = "free_x") +
stat_summary(geom = "crossbar", fun = mean, color = "red") +
stat_summary(geom = "crossbar", fun = median, color = "blue") +
geom_point(
alpha = 0.1,
position = position_jitter(width = 0.1, height = 0, seed = 1)
) +
geom_line(
aes(group = sub_id), alpha = 0.25,
position = position_jitter(width = 0.1, height = 0, seed = 1)
) +
xlab("Measurement") +
ylab("Search threshold") +
ggtitle("BFS-forward")
plot_params_ideal_obs <- params %>%
filter(model == "ideal_obs") %>%
pivot_wider(names_from = param_name, values_from = param_value) %>%
ggplot(aes(x=measurement_id, y=softmax_temperature)) +
theme_custom() +
facet_wrap(~study, scales = "free_x") +
stat_summary(geom = "crossbar", fun = mean, color = "red") +
stat_summary(geom = "crossbar", fun = median, color = "blue") +
geom_point(
alpha = 0.1,
position = position_jitter(width = 0.1, height = 0, seed = 1)
) +
geom_line(
aes(group = sub_id), alpha = 0.25,
position = position_jitter(width = 0.1, height = 0, seed = 1)
) +
xlab("Measurement") +
ylab("Inverse temperature") +
ggtitle("Ideal observer")
plot_params_sr_temp <- params %>%
filter(model == "sr_analytic") %>%
pivot_wider(names_from = param_name, values_from = param_value) %>%
ggplot(aes(x=measurement_id, y=softmax_temperature)) +
theme_custom() +
facet_wrap(~study, scales = "free_x") +
stat_summary(geom = "crossbar", fun = mean, color = "red") +
stat_summary(geom = "crossbar", fun = median, color = "blue") +
geom_point(
alpha = 0.1,
position = position_jitter(width = 0.1, height = 0, seed = 1)
) +
geom_line(
aes(group = sub_id), alpha = 0.25,
position = position_jitter(width = 0.1, height = 0, seed = 1)
) +
xlab("Measurement") +
ylab("Inverse temperature") +
ggtitle("Successor Representation") +
coord_cartesian(ylim = c(0, 3000))
plot_params_sr_gamma <- params %>%
filter(model == "sr_analytic") %>%
pivot_wider(names_from = param_name, values_from = param_value) %>%
ggplot(aes(x=measurement_id, y=sr_gamma)) +
theme_custom() +
facet_wrap(~study, scales = "free_x") +
stat_summary(geom = "crossbar", fun = mean, color = "red") +
stat_summary(geom = "crossbar", fun = median, color = "blue") +
geom_point(
alpha = 0.1,
position = position_jitter(width = 0.1, height = 0, seed = 1)
) +
geom_line(
aes(group = sub_id), alpha = 0.25,
position = position_jitter(width = 0.1, height = 0, seed = 1)
) +
xlab("Measurement") +
ylab("Gamma") +
ggtitle("Successor Representation")
plot_params_all <- (
(plot_params_bfs_backward | plot_params_bfs_forward | plot_params_ideal_obs) /
(plot_params_sr_temp | plot_params_sr_gamma)
) +
plot_annotation(
title = "Estimated parameters",
tag_levels = "A", tag_suffix = ".",
theme = theme(plot.title = element_text(hjust = 0.5))
)
plot_params_all
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?

if (knitting) {
ggsave(
filename = here("outputs", workflow_name, "param_estimates.pdf"),
plot = plot_params_all,
width = 12, height = 6,
units = "in", dpi = 300
)
}
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
Akaike weights
We’ll later use protected exceedance probabilities (PXP) to do formal
inference to test whether a particular model provides a significantly
better group-level fit than other models. But first, we do want to
acknowledge that there’s likely to be some individual differences in how
well a particular model fits each subject. To get a sense for this,
we’ll use Akaike weights, which provide the probability that a
particular model is the “best” given the data and the set of candidate
models.
akaike_weights <- aicc %>%
filter(model != "sr_delta_rule") %>%
group_by(study, measurement_id, sub_id) %>%
mutate(
relative_likelihood = exp(-1/2 * (aicc - min(aicc))),
akaike_weight = relative_likelihood / sum(relative_likelihood),
evidence_ratio = max(akaike_weight) / akaike_weight
) %>%
ungroup() %>%
arrange(study, measurement_id, sub_id, evidence_ratio)
We can first average over all subjects’ Akaike weights to get a sense
for what the “best-fitting” model is across subjects. This suggests that
the SR consistently comes out on top, followed pretty consistently by
BFS-backward.
plot_akaike_group <- akaike_weights %>%
group_by(study, measurement_id, model) %>%
summarise(akaike_weight = mean(akaike_weight), .groups = "drop") %>%
mutate(text = round(akaike_weight, 2)) %>%
ggplot(aes(x=measurement_id, y=akaike_weight, fill=model)) +
theme_custom() +
facet_wrap(~study, scales = "free_x") +
geom_col() +
geom_text(aes(label = text), position = position_stack(vjust = 0.5)) +
scale_x_discrete(
name = NULL,
labels = c(
"D1"="Before\nrest",
"D2"="After\novernight\nrest",
"D1b"="After\nawake\nrest"
)
) +
scale_y_continuous(
name = NULL,
expand = expansion(mult = c(0.01, 0.01))
) +
scale_fill_manual(
name = NULL,
labels = c(
"bfs_backward" = "BFS-backward",
"bfs_forward" = "BFS-forward",
"ideal_obs" = "Ideal observer",
"sr_analytic" = "Successor Representation"
),
values = c(
"bfs_backward" = "#a6dba0",
"bfs_forward" = "#5aae61",
"ideal_obs" = "#1b7837",
"sr_analytic" = "#af8dc3"
)
) +
theme(
legend.position = "bottom",
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
) +
ggtitle("Akaike weights")
plot_akaike_group

if (knitting) {
ggsave(
filename = here("outputs", workflow_name, "akaike_weights_group.pdf"),
plot = plot_akaike_group,
width = 6, height = 4,
units = "in", dpi = 300
)
}
We can break this out and plot each individual subject’s Akaike
weights.
plot_akaike_individual <- akaike_weights %>%
mutate(
sub_id = factor(sub_id),
measurement_id = case_when(
measurement_id == "D1" ~ "before rest",
measurement_id == "D1b" ~ "after awake rest",
measurement_id == "D2" ~ "after overnight rest"
),
study = str_c(study, ", ", measurement_id),
study = fct_relevel(
study,
"Study 1, before rest",
"Study 2, before rest",
"Study 2, after overnight rest",
"Study 3, before rest",
"Study 3, after awake rest",
"Study 3, after overnight rest"
)
) %>%
ggplot(aes(x=sub_id, y=akaike_weight, fill=model)) +
theme_custom() +
facet_wrap(~study, scales = "free_x", ncol = 1) +
geom_col() +
scale_x_discrete(name = "Subject ID") +
scale_y_continuous(
name = NULL,
expand = expansion(mult = c(0.01, 0.01))
) +
scale_fill_manual(
name = NULL,
labels = c(
"bfs_backward" = "BFS-backward",
"bfs_forward" = "BFS-forward",
"ideal_obs" = "Ideal observer",
"sr_analytic" = "Successor Representation"
),
values = c(
"bfs_backward" = "#a6dba0",
"bfs_forward" = "#5aae61",
"ideal_obs" = "#1b7837",
"sr_analytic" = "#af8dc3"
)
) +
theme(
legend.position = "bottom",
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
) +
ggtitle("Akaike weights")
plot_akaike_individual

if (knitting) {
ggsave(
filename = here(
"outputs", workflow_name,
"akaike_weights_per_subject.pdf"
),
plot = plot_akaike_individual,
width = 8, height = 10,
units = "in", dpi = 300
)
}
Akaike weights provide a nice goodness-of-fit metric that respects
the probabilistic aspect of model comparison, and can do so at both the
group- and individual-level. However, model selection requires us to
ultimately make a discrete choice. If we made per-subject decisions
based simply by choosing the single best-fitting model, what proportion
of subjects are best-fit by each model? We see that the pattern of
results basically mirrors what we’d seen in the Akaike weights, such
that the SR is the best-fitting model for the majority of subjects,
followed by BFS-backward.
best_fitting_model_per_sub <- akaike_weights %>%
group_by(study, measurement_id, sub_id) %>%
slice_max(akaike_weight) %>%
ungroup() %>%
select(study, measurement_id, sub_id, best_fitting_model = model)
plot_best_fitting_model_prop <- best_fitting_model_per_sub %>%
count(study, measurement_id, best_fitting_model) %>%
group_by(study, measurement_id) %>%
mutate(
p = n / sum(n),
text = str_c(round(p, 2) * 100, "%")
) %>%
ggplot(aes(x=measurement_id, y=p, fill=best_fitting_model)) +
theme_custom() +
facet_wrap(~study, scales = "free_x") +
geom_col() +
geom_text(aes(label = text), position = position_stack(vjust = 0.5)) +
scale_x_discrete(
name = NULL,
labels = c(
"D1"="Before\nrest",
"D2"="After\novernight\nrest",
"D1b"="After\nawake\nrest"
)
) +
scale_y_continuous(
name = NULL,
expand = expansion(mult = c(0.01, 0.01))
) +
scale_fill_manual(
name = NULL,
labels = c(
"bfs_backward" = "BFS-backward",
"bfs_forward" = "BFS-forward",
"ideal_obs" = "Ideal observer",
"sr_analytic" = "Successor Representation"
),
values = c(
"bfs_backward" = "#a6dba0",
"bfs_forward" = "#5aae61",
"ideal_obs" = "#1b7837",
"sr_analytic" = "#af8dc3"
)
) +
theme(
legend.position = "bottom",
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
) +
ggtitle("Proportion of subjects best fit by each model")
plot_best_fitting_model_prop

if (knitting) {
ggsave(
filename = here(
"outputs", workflow_name,
"prop_subjects_best_fit.pdf"
),
plot = plot_best_fitting_model_prop,
width = 6, height = 4,
units = "in", dpi = 300
)
}
Protected exceedance probabilities
Protected exceedance probabilities provide a formal test of a model’s
group-level fit compared to other candidate models. To run this
analysis, we’ll adapt software originally written by Matteo Lisi (https://github.com/mattelisi/bmsR). We’ll use AICc as
our metric of log-evidence.
pxp_results <- aicc %>%
filter(model != "sr_delta_rule") %>%
# In PXP, more is more. AICc, in contrast, is based off neg-LL, and so is
# interpreted as "smaller is better". So, do a sign flip.
mutate(aicc = -aicc) %>%
select(study, measurement_id, sub_id, model, aicc) %>%
# Compute PXP for each study/measurement
pivot_wider(names_from = model, values_from = aicc) %>%
select(-sub_id) %>%
group_by(study, measurement_id) %>%
nest() %>%
mutate(
test = map(
.x = data,
.f = ~bayesian_model_selection(.x)
)
) %>%
ungroup() %>%
unnest(test) %>%
select(-data)
In the results, it’s clear that the SR comes out on top, and by a
large margin.
pxp_results %>%
mutate(
measurement_id = case_when(
measurement_id == "D1" ~ "before rest",
measurement_id == "D1b" ~ "after awake rest",
measurement_id == "D2" ~ "after overnight rest"
),
study = str_c(study, ", ", measurement_id),
study = fct_relevel(
study,
"Study 1, before rest",
"Study 2, before rest",
"Study 2, after overnight rest",
"Study 3, before rest",
"Study 3, after awake rest",
"Study 3, after overnight rest"
)
) %>%
select(-measurement_id) %>%
arrange(study, desc(pxp)) %>%
kable_custom("PXP results", grouping_var = study)
PXP results
|
model
|
alpha
|
expected_model_frequencies
|
omnibus_risk
|
xp
|
pxp
|
|
Study 1, before rest
|
|
sr_analytic
|
36.549
|
0.677
|
0.000
|
0.999459
|
9.994587e-01
|
|
bfs_backward
|
14.010
|
0.259
|
0.000
|
0.000541
|
5.410882e-04
|
|
bfs_forward
|
1.475
|
0.027
|
0.000
|
0.000000
|
8.835920e-08
|
|
ideal_obs
|
1.967
|
0.036
|
0.000
|
0.000000
|
8.835920e-08
|
|
Study 2, before rest
|
|
sr_analytic
|
28.381
|
0.526
|
0.011
|
0.983780
|
9.758328e-01
|
|
bfs_backward
|
14.577
|
0.270
|
0.011
|
0.015947
|
1.848192e-02
|
|
ideal_obs
|
8.476
|
0.157
|
0.011
|
0.000273
|
2.977680e-03
|
|
bfs_forward
|
2.566
|
0.048
|
0.011
|
0.000000
|
2.707637e-03
|
|
Study 2, after overnight rest
|
|
sr_analytic
|
32.103
|
0.595
|
0.000
|
0.998161
|
9.981347e-01
|
|
bfs_backward
|
13.044
|
0.242
|
0.000
|
0.001818
|
1.826722e-03
|
|
ideal_obs
|
7.667
|
0.142
|
0.000
|
0.000021
|
2.978537e-05
|
|
bfs_forward
|
1.186
|
0.022
|
0.000
|
0.000000
|
8.786111e-06
|
|
Study 3, before rest
|
|
sr_analytic
|
26.939
|
0.539
|
0.005
|
0.976547
|
9.726377e-01
|
|
bfs_backward
|
14.387
|
0.288
|
0.005
|
0.023440
|
2.465903e-02
|
|
ideal_obs
|
5.363
|
0.107
|
0.005
|
0.000012
|
1.357085e-03
|
|
bfs_forward
|
3.312
|
0.066
|
0.005
|
0.000001
|
1.346145e-03
|
|
Study 3, after awake rest
|
|
sr_analytic
|
32.674
|
0.653
|
0.001
|
0.999898
|
9.994390e-01
|
|
ideal_obs
|
9.419
|
0.188
|
0.001
|
0.000102
|
2.549721e-04
|
|
bfs_backward
|
5.342
|
0.107
|
0.001
|
0.000000
|
1.530346e-04
|
|
bfs_forward
|
2.565
|
0.051
|
0.001
|
0.000000
|
1.530346e-04
|
|
Study 3, after overnight rest
|
|
sr_analytic
|
30.775
|
0.615
|
0.001
|
0.999712
|
9.988405e-01
|
|
ideal_obs
|
9.425
|
0.188
|
0.001
|
0.000237
|
5.273355e-04
|
|
bfs_backward
|
7.940
|
0.159
|
0.001
|
0.000051
|
3.415517e-04
|
|
bfs_forward
|
1.861
|
0.037
|
0.001
|
0.000000
|
2.906110e-04
|
plot_pxp <- pxp_results %>%
mutate(
text = round(pxp, 2) * 100,
text = if_else(model != "sr_analytic", "", str_c(text, "%"))
) %>%
ggplot(aes(x=measurement_id, y=pxp, fill=model)) +
theme_custom() +
facet_wrap(~study, scales = "free_x") +
geom_col() +
geom_text(aes(label = text), position = position_stack(vjust = 0.5)) +
scale_x_discrete(
name = NULL,
labels = c(
"D1"="Before\nrest",
"D2"="After\novernight\nrest",
"D1b"="After\nawake\nrest"
)
) +
scale_y_continuous(
name = NULL,
expand = expansion(mult = c(0.01, 0.01))
) +
scale_fill_manual(
name = NULL,
labels = c(
"bfs_backward" = "BFS-backward",
"bfs_forward" = "BFS-forward",
"ideal_obs" = "Ideal observer",
"sr_analytic" = "Successor Representation"
),
values = c(
"bfs_backward" = "#a6dba0",
"bfs_forward" = "#5aae61",
"ideal_obs" = "#1b7837",
"sr_analytic" = "#af8dc3"
)
) +
theme(
legend.position = "bottom",
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
) +
ggtitle("Protected exceedance probabilities")
plot_pxp

if (knitting) {
ggsave(
filename = here(
"outputs", workflow_name,
"pxp.pdf"
),
plot = plot_pxp,
width = 6, height = 4,
units = "in", dpi = 300
)
}
Posterior predictive check
Simulate predicted behaviors
It’s nice to see consistency in the quantitative model
comparison and selection, but we’d also like to see how well human
behaviors are qualitatively described by our models. To do
this, we’ll simulate subjects’ predicted behaviors given their model
parameters.
ppc_bfs_backward <- expand_grid(
# List of all trials from BFS simulation for each subject/measurement
params %>%
select(study, sub_id, measurement_id) %>%
distinct(),
bfs_backward_sims
) %>%
# Add subject-specific parameters
left_join(
params %>%
filter(model == "bfs_backward") %>%
pivot_wider(names_from = param_name, values_from = param_value) %>%
select(study, sub_id, measurement_id, search_threshold),
by = join_by(study, sub_id, measurement_id)
) %>%
# What's the probability of *completing* BFS-online all the way through?
rowwise() %>%
mutate(
p_complete_bfs = softmax(
option_values = c(search_threshold, bfs_visits),
option_chosen = 1,
temperature = 1
)
) %>%
ungroup() %>%
# Weigh BFS predictions accordingly
mutate(
p_give_up = 1 - p_complete_bfs,
model_p_correct = (
(p_complete_bfs * p_bfs_correct) + (p_give_up * 1/2)
)
) %>%
# Add subjects' actual choices
left_join(
bind_rows(nav_study1, nav_study2, nav_study3) %>%
select(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
sub_choice, sub_correct = correct, sub_rt = rt
),
by = join_by(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
)
)
ppc_bfs_forward <- expand_grid(
# List of all trials from BFS simulation for each subject/measurement
params %>%
select(study, sub_id, measurement_id) %>%
distinct(),
bfs_forward_sims
) %>%
# Add subject-specific parameters
left_join(
params %>%
filter(model == "bfs_forward") %>%
pivot_wider(names_from = param_name, values_from = param_value) %>%
select(study, sub_id, measurement_id, search_threshold),
by = join_by(study, sub_id, measurement_id)
) %>%
# What's the probability of *completing* BFS-online all the way through?
rowwise() %>%
mutate(
p_complete_bfs = softmax(
option_values = c(search_threshold, bfs_visits),
option_chosen = 1,
temperature = 1
)
) %>%
ungroup() %>%
# Weigh BFS predictions accordingly
mutate(
p_give_up = 1 - p_complete_bfs,
model_p_correct = (
(p_complete_bfs * p_bfs_correct) + (p_give_up * 1/2)
)
) %>%
# Add subjects' actual choices
left_join(
bind_rows(nav_study1, nav_study2, nav_study3) %>%
select(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
sub_choice, sub_correct = correct, sub_rt = rt
),
by = join_by(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
)
)
ppc_ideal_obs <- expand_grid(
# List of all trials for each subject/measurement
params %>%
select(study, sub_id, measurement_id) %>%
distinct(),
nav_trials
) %>%
# Add subject-specific parameters
left_join(
params %>%
filter(model == "ideal_obs") %>%
pivot_wider(names_from = param_name, values_from = param_value) %>%
select(study, sub_id, measurement_id, softmax_temperature),
by = join_by(study, sub_id, measurement_id)
) %>%
# Model predictions
rowwise() %>%
mutate(
model_p_correct = softmax(
option_values = c(opt1_distance, opt2_distance),
option_chosen = if_else(correct_choice == opt1_id, 1, 2),
temperature = softmax_temperature,
use_inverse_temperature = TRUE
)
) %>%
ungroup() %>%
# Add subjects' actual choices
left_join(
bind_rows(nav_study1, nav_study2, nav_study3) %>%
select(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
sub_choice, sub_correct = correct, sub_rt = rt
),
by = join_by(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
)
)
ppc_sr_representation <- params %>%
filter(model == "sr_analytic") %>%
pivot_wider(names_from = param_name, values_from = param_value) %>%
select(study, sub_id, measurement_id, sr_gamma) %>%
rowwise() %>%
mutate(
predicted_sr = map(
.x = sr_gamma,
.f = ~build_successor_analytically(
transmat, successor_horizon = .x, normalize = TRUE
)
)
) %>%
ungroup() %>%
select(study, sub_id, measurement_id, predicted_sr) %>%
unnest(predicted_sr)
ppc_sr_navigation <- expand_grid(
# List of all trials for each subject/measurement
params %>%
select(study, sub_id, measurement_id) %>%
distinct(),
nav_trials
) %>%
# Add subject-specific parameters
left_join(
params %>%
filter(model == "sr_analytic") %>%
pivot_wider(names_from = param_name, values_from = param_value) %>%
select(study, sub_id, measurement_id, softmax_temperature),
by = join_by(study, sub_id, measurement_id)
) %>%
# Add SR predicted representation
left_join(
ppc_sr_representation %>%
select(
study, sub_id, measurement_id,
endpoint_id = to, opt1_id = from, opt1_sr = sr_value
),
by = join_by(study, sub_id, measurement_id, endpoint_id, opt1_id)
) %>%
left_join(
ppc_sr_representation %>%
select(
study, sub_id, measurement_id,
endpoint_id = to, opt2_id = from, opt2_sr = sr_value
),
by = join_by(study, sub_id, measurement_id, endpoint_id, opt2_id)
) %>%
# Model navigation predictions
rowwise() %>%
mutate(
model_p_correct = softmax(
option_values = c(opt1_sr, opt2_sr),
option_chosen = if_else(correct_choice == opt1_id, 1, 2),
temperature = softmax_temperature,
use_inverse_temperature = TRUE
)
) %>%
ungroup() %>%
# Add subjects' actual choices
left_join(
bind_rows(nav_study1, nav_study2, nav_study3) %>%
select(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
sub_choice, sub_correct = correct, sub_rt = rt
),
by = join_by(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
)
)
Plot PPC
ppc_for_plotting <- params %>%
select(study, sub_id, measurement_id) %>%
distinct() %>%
# Add human accuracy + BFS-backward accuracy
left_join(
ppc_bfs_backward %>%
filter(two_correct_options == FALSE) %>%
group_by(study, sub_id, measurement_id, shortest_path) %>%
summarise(
human = mean(sub_correct),
bfs_backward = mean(model_p_correct),
.groups = "drop"
),
by = join_by(study, sub_id, measurement_id)
) %>%
# Add BFS-forward accuracy
left_join(
ppc_bfs_forward %>%
filter(two_correct_options == FALSE) %>%
group_by(study, sub_id, measurement_id, shortest_path) %>%
summarise(
bfs_forward = mean(model_p_correct),
.groups = "drop"
),
by = join_by(study, sub_id, measurement_id, shortest_path)
) %>%
# Add ideal observer accuracy
left_join(
ppc_ideal_obs %>%
filter(two_correct_options == FALSE) %>%
group_by(study, sub_id, measurement_id, shortest_path) %>%
summarise(
ideal_obs = mean(model_p_correct),
.groups = "drop"
),
by = join_by(study, sub_id, measurement_id, shortest_path)
) %>%
# Add SR accuracy
left_join(
ppc_sr_navigation %>%
filter(two_correct_options == FALSE) %>%
group_by(study, sub_id, measurement_id, shortest_path) %>%
summarise(
sr = mean(model_p_correct),
.groups = "drop"
),
by = join_by(study, sub_id, measurement_id, shortest_path)
) %>%
# For plotting aesthetics
pivot_longer(human:sr, names_to = "agent", values_to = "accuracy") %>%
mutate(
agent = case_when(
agent == "human" ~ "Human",
agent == "bfs_backward" ~ "BFS-backward",
agent == "bfs_forward" ~ "BFS-forward",
agent == "ideal_obs" ~ "Ideal observer",
agent == "sr" ~ "Successor Rep."
),
agent = fct_relevel(agent, "Human", "Successor Rep.")
)
Let’s look at the main set of trials and compare human performance
against the models on Day 1 (i.e., before rest).
plot_ppc_day1 <- ppc_for_plotting %>%
filter(measurement_id == "D1") %>%
ggplot(aes(x=shortest_path, y=accuracy)) +
theme_custom() +
facet_wrap(~agent, nrow = 1) +
geom_hline(yintercept = 0.5, linetype = "dashed", color = "blue") +
geom_line(aes(group = interaction(study, sub_id)), alpha = 0.1) +
stat_summary(geom = "crossbar", fun = mean, color = "red") +
scale_x_discrete(name = "Shortest path distance") +
scale_y_continuous(name = "Accuracy", labels = scales::percent) +
ggtitle("Posterior predictive check: Before rest")
plot_ppc_day1

if (knitting) {
ggsave(
filename = here("outputs", workflow_name, "ppc_day1.pdf"),
plot = plot_ppc_day1,
width = 6, height = 3,
units = "in", dpi = 300
)
}
We’ll do the same now for Day 2 (i.e., after overnight rest).
plot_ppc_day2 <- ppc_for_plotting %>%
filter(measurement_id == "D2") %>%
ggplot(aes(x=shortest_path, y=accuracy)) +
theme_custom() +
facet_wrap(~agent, nrow = 1) +
geom_hline(yintercept = 0.5, linetype = "dashed", color = "blue") +
geom_line(aes(group = interaction(study, sub_id)), alpha = 0.1) +
stat_summary(geom = "crossbar", fun = mean, color = "red") +
scale_x_discrete(name = "Shortest path distance") +
scale_y_continuous(name = "Accuracy", labels = scales::percent) +
ggtitle("Posterior predictive check: After overnight rest")
plot_ppc_day2

if (knitting) {
ggsave(
filename = here("outputs", workflow_name, "ppc_day2.pdf"),
plot = plot_ppc_day2,
width = 6, height = 3,
units = "in", dpi = 300
)
}
And finally, for Day 1b (i.e., after awake rest on Day 1), a
measurement that was only made in Study 3.
plot_ppc_day1b <- ppc_for_plotting %>%
filter(measurement_id == "D1b") %>%
ggplot(aes(x=shortest_path, y=accuracy)) +
theme_custom() +
facet_wrap(~agent, nrow = 1) +
geom_hline(yintercept = 0.5, linetype = "dashed", color = "blue") +
geom_line(aes(group = interaction(study, sub_id)), alpha = 0.1) +
stat_summary(geom = "crossbar", fun = mean, color = "red") +
scale_x_discrete(name = "Shortest path distance") +
scale_y_continuous(name = "Accuracy", labels = scales::percent) +
ggtitle("Posterior predictive check: After awake rest")
plot_ppc_day1b

if (knitting) {
ggsave(
filename = here("outputs", workflow_name, "ppc_day1b.pdf"),
plot = plot_ppc_day1b,
width = 6, height = 3,
units = "in", dpi = 300
)
}
Held-out trials
The primary analyses, including the parameter-fitting, were performed
on a set of trials where there was always one unambiguously correct
answer. For this reason, there was also a subset of trials that got
“held out” because the two options had the same shortest path distance
from the target.
Several of the computational models (i.e., BFS-backward and ideal
observer) therefore predict 50/50% choices on these trials. BFS-forward
does not, as it allows for stochasticity in how agents perform searches
from each of the two options. Most notably, the SR often predicts that
an agent will prefer one option over another, which basically reflects
the fact that (e.g.) although Sources A and B have the same shortest
path distance to the Target, Source A might have a greater number of
short paths to the Target.
Therefore, if subjects’ behaviors are consistent with non-random
responding, we’d ideally like to see that the SR is able to make more
accurate out-of-sample predictions on these trials.
heldout_likelihoods <- ppc_sr_navigation %>%
filter(two_correct_options == TRUE) %>%
select(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
opt1_sr, opt2_sr, sub_choice, softmax_temperature
) %>%
mutate(
sr_prefers = case_when(
opt1_sr == opt2_sr ~ NA_real_,
opt1_sr > opt2_sr ~ opt1_id,
TRUE ~ opt2_id
)
) %>%
# Calculate the likelihood of the subject's choice, given what option
# the SR would have preferred
rowwise() %>%
mutate(
p_sub_choice = softmax(
option_values = c(opt1_sr, opt2_sr),
option_chosen = if_else(sr_prefers == opt1_id, 1, 2),
temperature = softmax_temperature,
use_inverse_temperature = TRUE
),
# Fix a few edge cases
p_sub_choice = case_when(
is.na(sr_prefers) ~ 0.5,
is.nan(p_sub_choice) & (sub_choice == sr_prefers) ~ 1,
# To avoid log(0), use machine epsilon
is.nan(p_sub_choice) & (sub_choice != sr_prefers) ~ 2.22e-16,
TRUE ~ p_sub_choice
)
) %>%
ungroup() %>%
mutate(neg_ll_sr = neg_loglik_logistic(p_sub_choice)) %>%
# Tidy up the SR bit
select(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
sr_prefers, sub_choice,
neg_ll_sr
) %>%
# Add likelihoods for BFS-backward, which always predicts 50/50 responding
mutate(neg_ll_bfs_backward = neg_loglik_logistic(0.5)) %>%
# Add likelihoods for BFS-forward
left_join(
ppc_bfs_forward %>%
select(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
p_bfs_chooses_opt1, p_complete_bfs, p_give_up
),
by = join_by(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
)
) %>%
mutate(
p_sub_choice = case_when(
is.na(sr_prefers) ~ 0.5,
sr_prefers == opt1_id ~ (
(p_complete_bfs * p_bfs_chooses_opt1) + (p_give_up * 1/2)
),
sr_prefers == opt2_id ~ (
(p_complete_bfs * (1-p_bfs_chooses_opt1)) + (p_give_up * 1/2)
)
),
neg_ll_bfs_forward = neg_loglik_logistic(p_sub_choice)
) %>%
# Add likelihoods for ideal observer, which always predicts 50/50 responding
mutate(neg_ll_ideal_obs = neg_loglik_logistic(0.5)) %>%
# Tidy up
select(
study, sub_id, measurement_id,
shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
sr_prefers, sub_choice,
neg_ll_sr, neg_ll_bfs_backward, neg_ll_bfs_forward, neg_ll_ideal_obs
)
Below, we see that the SR, compared to the other models, is doing a
better job of explaining subjects’ choices on held-out trials, though we
again note that the likelihoods for the BFS-backward and ideal observer
models are for completely random responding. The different studies have
different baseline log-likelihoods because they contain a different
number of trials (i.e., in Studies 2-3, we’re summing over both Day 1
and Day 2 measurements).
plot_heldout_likelihoods <- heldout_likelihoods %>%
# Sum so that we get one neg-loglik per subject
group_by(study, sub_id, measurement_id) %>%
summarise(across(starts_with("neg_ll_"), sum), .groups = "drop") %>%
# Prep for plotting
pivot_longer(
starts_with("neg_ll_"), names_to = "model", values_to = "neg_ll"
) %>%
mutate(model = str_remove(model, "neg_ll_"), loglik = -neg_ll) %>%
ggplot(aes(x=measurement_id, y=loglik, color=model)) +
theme_custom() +
facet_wrap(~study, scales = "free_x") +
stat_summary(
geom = "crossbar", fun = median, position = position_dodge(width = 0.75)
) +
geom_point(
alpha = 0.2,
position = position_jitterdodge(
jitter.width = 0.1, jitter.height = 0, dodge.width = 0.75, seed = 1
),
show.legend = FALSE
) +
scale_x_discrete(
name = NULL,
labels = c(
"D1"="Before\nrest",
"D2"="After\novernight\nrest",
"D1b"="After\nawake\nrest"
)
) +
scale_y_continuous(name = "log-likelihood\n(greater = better)") +
scale_color_manual(
name = NULL,
labels = c(
"bfs_backward" = "BFS-backward",
"bfs_forward" = "BFS-forward",
"ideal_obs" = "Ideal observer",
"sr" = "Successor Rep."
),
values = c(
"bfs_backward" = "#a6dba0",
"bfs_forward" = "#5aae61",
"ideal_obs" = "#1b7837",
"sr" = "#af8dc3"
)
) +
theme(legend.position = "bottom") +
ggtitle("Held-out trials: log-likelihoods")
plot_heldout_likelihoods

if (knitting) {
ggsave(
filename = here("outputs", workflow_name, "heldout_likelihoods.pdf"),
plot = plot_heldout_likelihoods,
width = 6, height = 4,
units = "in", dpi = 300
)
}
Although the likelihoods give us a nice quantitative metric, we may
also be interested in knowing how well the SR predicts subjects’ choices
just in terms of accuracy. In the below analysis, we’ll use
mixed-effects logistic regression to test whether subjects significantly
choose the SR-preferred option. Note that we’re removing all of the
trials where the SR is indifferent to the two options, as those trials
lead us to overestimate the model’s predicted accuracy (i.e., because
the subject is always right on those trials). Note also that we’re
iteratively re-parameterizing the model with a different reference
category each time, so that we can test whether model accuracy is
significantly above chance at each distance. Finally, note that for the
purpose of statistical testing, we’re looking at the two main
measurements: before rest (day 1), and after overnight rest (day 2).
stats_heldout_accuracy_dist2 <- heldout_likelihoods %>%
filter(measurement_id %in% c("D1", "D2")) %>%
filter(!is.na(sr_prefers)) %>%
mutate(
p_sub_chooses_sr_preference = sub_choice == sr_prefers,
sub_id = str_c(study, ", s", sub_id)
) %>%
glmmTMB(
p_sub_chooses_sr_preference ~ shortest_path +
(1 | sub_id) + (1 | study),
family = binomial,
data = .
)
stats_heldout_accuracy_dist3 <- heldout_likelihoods %>%
filter(measurement_id %in% c("D1", "D2")) %>%
filter(!is.na(sr_prefers)) %>%
mutate(
p_sub_chooses_sr_preference = sub_choice == sr_prefers,
sub_id = str_c(study, ", s", sub_id),
shortest_path = fct_relevel(shortest_path, "3")
) %>%
glmmTMB(
p_sub_chooses_sr_preference ~ shortest_path +
(1 | sub_id) + (1 | study),
family = binomial,
data = .
)
stats_heldout_accuracy_dist4 <- heldout_likelihoods %>%
filter(measurement_id %in% c("D1", "D2")) %>%
filter(!is.na(sr_prefers)) %>%
mutate(
p_sub_chooses_sr_preference = sub_choice == sr_prefers,
sub_id = str_c(study, ", s", sub_id),
shortest_path = fct_relevel(shortest_path, "4")
) %>%
glmmTMB(
p_sub_chooses_sr_preference ~ shortest_path +
(1 | sub_id) + (1 | study),
family = binomial,
data = .
)
map_dfr(
.x = list(
"dist2" = stats_heldout_accuracy_dist2,
"dist3" = stats_heldout_accuracy_dist3,
"dist4" = stats_heldout_accuracy_dist4
),
.f = ~tidy(.x) %>% select(-component),
.id = "ref_cat"
) %>%
kable_custom(
"Held-out trials: SR model accuracy",
grouping_var = ref_cat
)
Held-out trials: SR model accuracy
|
effect
|
group
|
term
|
estimate
|
std.error
|
statistic
|
p.value
|
|
dist2
|
|
fixed
|
NA
|
(Intercept)
|
0.002
|
0.058
|
0.036
|
0.971
|
|
fixed
|
NA
|
shortest_path3
|
0.307
|
0.085
|
3.618
|
0.000
|
|
fixed
|
NA
|
shortest_path4
|
0.196
|
0.098
|
1.993
|
0.046
|
|
ran_pars
|
sub_id
|
sd__(Intercept)
|
0.289
|
NA
|
NA
|
NA
|
|
ran_pars
|
study
|
sd__(Intercept)
|
0.000
|
NA
|
NA
|
NA
|
|
dist3
|
|
fixed
|
NA
|
(Intercept)
|
0.309
|
0.071
|
4.378
|
0.000
|
|
fixed
|
NA
|
shortest_path2
|
-0.307
|
0.085
|
-3.618
|
0.000
|
|
fixed
|
NA
|
shortest_path4
|
-0.111
|
0.106
|
-1.047
|
0.295
|
|
ran_pars
|
sub_id
|
sd__(Intercept)
|
0.289
|
NA
|
NA
|
NA
|
|
ran_pars
|
study
|
sd__(Intercept)
|
0.000
|
NA
|
NA
|
NA
|
|
dist4
|
|
fixed
|
NA
|
(Intercept)
|
0.198
|
0.086
|
2.294
|
0.022
|
|
fixed
|
NA
|
shortest_path2
|
-0.196
|
0.098
|
-1.993
|
0.046
|
|
fixed
|
NA
|
shortest_path3
|
0.111
|
0.106
|
1.047
|
0.295
|
|
ran_pars
|
sub_id
|
sd__(Intercept)
|
0.289
|
NA
|
NA
|
NA
|
|
ran_pars
|
study
|
sd__(Intercept)
|
0.000
|
NA
|
NA
|
NA
|
In the plot below, we can see that there’s quite a bit of variability
across subjects (black lines and datapoints), but also that at the group
level, the SR achieves above-chance accuracy for the held-out distance-3
and distance-4 trials (red).
predict_heldout_accuracy <- tibble(
shortest_path = factor(2:4), sub_id = NA, study = NA
) %>%
predict_glmmTMB(stats_heldout_accuracy_dist2)
plot_heldout_accuracy <- heldout_likelihoods %>%
filter(measurement_id %in% c("D1", "D2")) %>%
filter(!is.na(sr_prefers)) %>%
group_by(study, sub_id, shortest_path) %>%
summarise(
p_sub_chooses_sr_preference = mean(sub_choice == sr_prefers),
.groups = "drop"
) %>%
ggplot(aes(x=shortest_path, y=p_sub_chooses_sr_preference)) +
theme_custom() +
geom_hline(yintercept = 0.5, linetype = "dashed") +
geom_line(aes(group = interaction(study, sub_id)), alpha = 0.1) +
geom_point(alpha = 0.1) +
geom_pointrange(
aes(x = shortest_path, y = fit, ymin = fit - se.fit, ymax = fit + se.fit),
data = predict_heldout_accuracy, inherit.aes = FALSE,
# fatten = 1, size = 1,
linewidth = 1, color = "red"
) +
scale_x_discrete(name = "Shortest path distance") +
scale_y_continuous(
name = "p(Human chooses SR-preferred Source)",
labels = scales::percent,
breaks = seq(0, 1, .25),
expand = expansion(mult = c(0.1, 0.15))
) +
ggtitle("Held-out trials: SR model accuracy")
plot_heldout_accuracy

if (knitting) {
ggsave(
filename = here("outputs", workflow_name, "heldout_accuracy.pdf"),
plot = plot_heldout_accuracy,
width = 4, height = 5,
units = "in", dpi = 300
)
}
McFadden’s pseudo R-squared
Finally, it can be useful to get a sense for each model’s
“goodness-of-fit” by computing the ratio of its likelihood to the
likelihood of a null model (i.e., McFadden’s R-squared). Here, the null
model is an agent that chooses completely at random on every trial.
mcfadden_r2 <- aicc %>%
filter(model != "sr_delta_rule") %>%
mutate(loglik = -neg_loglik) %>%
select(study, sub_id, measurement_id, model, model_loglik = loglik) %>%
mutate(
# Null model is chance-level choice on every trial, so this ends up being
# exactly equal to the usual formulation
null_loglik = nav_trials %>%
filter(two_correct_options == FALSE) %>%
nrow() %>%
{. * log(0.5)}
) %>%
mutate(mcfadden_r2 = 1 - (model_loglik / null_loglik))
mcfadden_r2 %>%
group_by(study, measurement_id, model) %>%
summarise(Mean = mean(mcfadden_r2), .groups = "drop") %>%
pivot_wider(names_from = model, values_from = Mean) %>%
kable_custom("Mean McFadden's R2", grouping_var = study)
Mean McFadden’s R2
|
measurement_id
|
bfs_backward
|
bfs_forward
|
ideal_obs
|
sr_analytic
|
|
Study 1
|
|
D1
|
0.185
|
0.171
|
0.129
|
0.200
|
|
Study 2
|
|
D1
|
0.252
|
0.245
|
0.221
|
0.275
|
|
D2
|
0.310
|
0.291
|
0.293
|
0.335
|
|
Study 3
|
|
D1
|
0.290
|
0.273
|
0.266
|
0.311
|
|
D1b
|
0.281
|
0.271
|
0.276
|
0.306
|
|
D2
|
0.338
|
0.318
|
0.335
|
0.374
|
mcfadden_r2 %>%
group_by(study, measurement_id, model) %>%
summarise(Median = median(mcfadden_r2), .groups = "drop") %>%
pivot_wider(names_from = model, values_from = Median) %>%
kable_custom("Median McFadden's R2", grouping_var = study)
Median McFadden’s R2
|
measurement_id
|
bfs_backward
|
bfs_forward
|
ideal_obs
|
sr_analytic
|
|
Study 1
|
|
D1
|
0.114
|
0.124
|
0.063
|
0.151
|
|
Study 2
|
|
D1
|
0.184
|
0.172
|
0.138
|
0.195
|
|
D2
|
0.226
|
0.221
|
0.188
|
0.245
|
|
Study 3
|
|
D1
|
0.198
|
0.171
|
0.134
|
0.194
|
|
D1b
|
0.166
|
0.155
|
0.161
|
0.196
|
|
D2
|
0.237
|
0.218
|
0.209
|
0.268
|
We see in the plot that, generally, all models are doing better than
the null model, and also that the SR consistently seems to have the best
likelihood ratio.
plot_mcfadden_r2 <- mcfadden_r2 %>%
ggplot(aes(x=measurement_id, y=mcfadden_r2, color=model)) +
theme_custom() +
facet_wrap(~study, scales = "free_x") +
geom_point(
alpha = 0.1,
position = position_jitterdodge(
jitter.width = 0.1, jitter.height = 0, dodge.width = 0.75, seed = 1
),
show.legend = FALSE
) +
stat_summary(
geom = "crossbar", fun = median, position = position_dodge(width = 0.75)
) +
scale_x_discrete(
name = NULL,
labels = c(
"D1"="Before\nrest",
"D2"="After\novernight\nrest",
"D1b"="After\nawake\nrest"
)
) +
scale_y_continuous(name = bquote(~"McFadden's"~R^2)) +
scale_color_manual(
name = NULL,
labels = c(
"bfs_backward" = "BFS-backward",
"bfs_forward" = "BFS-forward",
"ideal_obs" = "Ideal observer",
"sr_analytic" = "Successor Representation"
),
values = c(
"bfs_backward" = "#a6dba0",
"bfs_forward" = "#5aae61",
"ideal_obs" = "#1b7837",
"sr_analytic" = "#af8dc3"
)
) +
theme(legend.position = "bottom") +
ggtitle("Model goodness-of-fit")
plot_mcfadden_r2

if (knitting) {
ggsave(
filename = here("outputs", workflow_name, "mcfadden_r2.pdf"),
plot = plot_mcfadden_r2,
width = 6, height = 4,
units = "in", dpi = 300
)
}
Figures for supplement
plot_model_comparison_for_supp <- wrap_plots(
plot_akaike_group,
plot_best_fitting_model_prop,
plot_pxp,
guides = "collect", nrow = 1
) +
plot_annotation(
title = "Model comparison",
tag_levels = "A", tag_suffix = ".",
theme = theme(plot.title = element_text(hjust = 0.5))
) &
theme(legend.position = "bottom")
plot_model_comparison_for_supp

if (knitting) {
ggsave(
filename = here("figures", "supp_model_comparison.pdf"),
plot = plot_model_comparison_for_supp,
width = 16, height = 4,
units = "in", dpi = 300
)
}
plot_ppc_for_supp <- wrap_plots(
plot_ppc_day2 + ggtitle("After overnight rest (Studies 2-3)"),
plot_ppc_day1b + ggtitle("After awake rest (Study 3)"),
ncol = 1
) +
plot_annotation(
title = "Posterior predictive check",
tag_levels = "A", tag_suffix = ".",
theme = theme(plot.title = element_text(hjust = 0.5))
) &
scale_y_continuous(
name = "Accuracy", labels = scales::percent,
limits = c(.25, 1),
breaks = seq(.25, 1, .25)
)
## Scale for y is already present.
## Adding another scale for y, which will replace the existing scale.
## Scale for y is already present.
## Adding another scale for y, which will replace the existing scale.
plot_ppc_for_supp

if (knitting) {
ggsave(
filename = here("figures", "supp_ppc.pdf"),
plot = plot_ppc_for_supp,
width = 8, height = 6,
units = "in", dpi = 300
)
}
There are some plots that we want to use as-is, so we’ll save a
redundant copy in the figures folder.
if (knitting) {
ggsave(
filename = here("figures", "supp_param_estimates.pdf"),
plot = plot_params_all,
width = 12, height = 6,
units = "in", dpi = 300
)
ggsave(
filename = here("figures", "supp_akaike_weights_per_subject.pdf"),
plot = plot_akaike_individual,
width = 8, height = 10,
units = "in", dpi = 300
)
ggsave(
filename = here("figures", "supp_heldout_likelihoods.pdf"),
plot = plot_heldout_likelihoods,
width = 6, height = 4,
units = "in", dpi = 300
)
ggsave(
filename = here("figures", "supp_heldout_accuracy.pdf"),
plot = plot_heldout_accuracy,
width = 4, height = 5,
units = "in", dpi = 300
)
ggsave(
filename = here("figures", "supp_mcfadden_r2.pdf"),
plot = plot_mcfadden_r2,
width = 6, height = 4,
units = "in", dpi = 300
)
}
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
LS0tCnRpdGxlOiAiTW9kZWwgY29tcGFyaXNvbiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IHRydWUKLS0tCgojIFNldHVwCgpgYGB7ciBsaWJyYXJpZXN9CndvcmtmbG93X25hbWUgPC0gIm5ldG5hdl8wNl9tb2RlbF9jb21wYXJpc29uIgoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoaGVyZSkKbGlicmFyeShwYXRjaHdvcmspCgpsaWJyYXJ5KGdsbW1UTUIpCmxpYnJhcnkoYnJvb20ubWl4ZWQpCgpzb3VyY2UoaGVyZSgiY29kZSIsICJ1dGlscyIsICJtb2RlbGluZ191dGlscy5SIikpCnNvdXJjZShoZXJlKCJjb2RlIiwgInV0aWxzIiwgInJlcHJlc2VudGF0aW9uX3V0aWxzLlIiKSkKc291cmNlKGhlcmUoImNvZGUiLCAidXRpbHMiLCAiYmF5ZXNpYW5fbW9kZWxfc2VsZWN0aW9uLlIiKSkKCnNvdXJjZShoZXJlKCJjb2RlIiwgInV0aWxzIiwgImdncGxvdF90aGVtZXMuUiIpKQpzb3VyY2UoaGVyZSgiY29kZSIsICJ1dGlscyIsICJrYWJsZV91dGlscy5SIikpCnNvdXJjZShoZXJlKCJjb2RlIiwgInV0aWxzIiwgInVuaWNvZGVfZ3JlZWsuUiIpKQoKa25pdHRpbmcgPC0ga25pdHI6OmlzX2h0bWxfb3V0cHV0KCkKCmNyZWF0ZV9wYXRoIDwtIGZ1bmN0aW9uKHRoaXNfcGF0aCkgewogIGlmICghZGlyLmV4aXN0cyh0aGlzX3BhdGgpKSB7CiAgICBkaXIuY3JlYXRlKHRoaXNfcGF0aCwgcmVjdXJzaXZlID0gVFJVRSkKICB9Cn0KCnByZWRpY3RfZ2xtbVRNQiA8LSBmdW5jdGlvbihtYWtlX3ByZWRpY3Rpb25zX2ZvciwgbW9kZWxfb2JqZWN0KSB7CiAgbWFrZV9wcmVkaWN0aW9uc19mb3IgJT4lCiAgICBiaW5kX2NvbHMoCiAgICAgIHByZWRpY3QoCiAgICAgICAgb2JqZWN0ID0gbW9kZWxfb2JqZWN0LAogICAgICAgIG5ld2RhdGEgPSAuLAogICAgICAgIHJlLmZvcm0gPSBOQSwgYWxsb3cubmV3LmxldmVscyA9IFRSVUUsIHNlLmZpdCA9IFRSVUUsIHR5cGUgPSAicmVzcG9uc2UiCiAgICAgICkKICAgICkKfQoKaWYgKGtuaXR0aW5nKSB7CiAgaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUpICU+JQogICAgY3JlYXRlX3BhdGgoKQogIAogIGhlcmUoImZpZ3VyZXMiKSAlPiUKICAgIGNyZWF0ZV9wYXRoKCkKfQpgYGAKCmBgYHtyIGxvYWQtYmVoYXYtZGF0YX0KbmF2X3N0dWR5MSA8LSBoZXJlKCJkYXRhIiwgImNsZWFuX2RhdGEiLCAic3R1ZHkxX21lc3NhZ2VfcGFzc2luZy5jc3YiKSAlPiUKICByZWFkX2NzdihzaG93X2NvbF90eXBlcyA9IEZBTFNFKSAlPiUKICBmaWx0ZXIoCiAgICAjIHR3b19jb3JyZWN0X29wdGlvbnMgPT0gRkFMU0UsCiAgICBzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMgPT0gc2hvcnRlc3RfcGF0aF9naXZlbl9zdGFydF9lbmQKICApICU+JQogIG11dGF0ZSgKICAgIHN0dWR5ID0gIlN0dWR5IDEiLAogICAgbWVhc3VyZW1lbnRfaWQgPSBzdHJfYygiRCIsIG1lYXN1cmVtZW50X2lkKSwKICAgIHNob3J0ZXN0X3BhdGggPSBmYWN0b3Ioc2hvcnRlc3RfcGF0aF9naXZlbl9vcHRzKQogICkgJT4lCiAgc2VsZWN0KAogICAgc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsIHNob3J0ZXN0X3BhdGgsCiAgICBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwKICAgIG9wdDFfaWQsIG9wdDJfaWQsCiAgICBjb3JyZWN0X2Nob2ljZSwgc3ViX2Nob2ljZSwKICAgIGNvcnJlY3QsIHJ0LAogICAgdHdvX2NvcnJlY3Rfb3B0aW9ucywKICAgIG9wdDFfZGlzdGFuY2UgPSBkaXN0X29wdDEsCiAgICBvcHQyX2Rpc3RhbmNlID0gZGlzdF9vcHQyCiAgKQoKbmF2X3N0dWR5MiA8LSBoZXJlKCJkYXRhIiwgImNsZWFuX2RhdGEiLCAic3R1ZHkyX21lc3NhZ2VfcGFzc2luZy5jc3YiKSAlPiUKICByZWFkX2NzdihzaG93X2NvbF90eXBlcyA9IEZBTFNFKSAlPiUKICBmaWx0ZXIoCiAgICAjIHR3b19jb3JyZWN0X29wdGlvbnMgPT0gRkFMU0UsCiAgICBzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMgPT0gc2hvcnRlc3RfcGF0aF9naXZlbl9zdGFydF9lbmQKICApICU+JQogIG11dGF0ZSgKICAgIHN0dWR5ID0gIlN0dWR5IDIiLAogICAgbWVhc3VyZW1lbnRfaWQgPSBjYXNlX3doZW4oCiAgICAgIG5ldHdvcmsgPT0gImxlYXJuZWQiIH4gc3RyX2MoIkQiLCBtZWFzdXJlbWVudF9pZCksCiAgICAgIG5ldHdvcmsgPT0gInJlZXZhbHVhdGVkIiB+ICJEMmIiCiAgICApLAogICAgc2hvcnRlc3RfcGF0aCA9IGZhY3RvcihzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMpCiAgKSAlPiUKICBzZWxlY3QoCiAgICBzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc2hvcnRlc3RfcGF0aCwKICAgIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLAogICAgb3B0MV9pZCwgb3B0Ml9pZCwKICAgIGNvcnJlY3RfY2hvaWNlLCBzdWJfY2hvaWNlLAogICAgY29ycmVjdCwgcnQsCiAgICB0d29fY29ycmVjdF9vcHRpb25zLAogICAgb3B0MV9kaXN0YW5jZSA9IGRpc3Rfb3B0MSwKICAgIG9wdDJfZGlzdGFuY2UgPSBkaXN0X29wdDIKICApCgpuYXZfc3R1ZHkzIDwtIGhlcmUoImRhdGEiLCAiY2xlYW5fZGF0YSIsICJzdHVkeTNfbWVzc2FnZV9wYXNzaW5nLmNzdiIpICU+JQogIHJlYWRfY3N2KHNob3dfY29sX3R5cGVzID0gRkFMU0UpICU+JQogIGZpbHRlcigKICAgICMgdHdvX2NvcnJlY3Rfb3B0aW9ucyA9PSBGQUxTRSwKICAgIHNob3J0ZXN0X3BhdGhfZ2l2ZW5fb3B0cyA9PSBzaG9ydGVzdF9wYXRoX2dpdmVuX3N0YXJ0X2VuZAogICkgJT4lCiAgbXV0YXRlKAogICAgc3R1ZHkgPSAiU3R1ZHkgMyIsCiAgICBtZWFzdXJlbWVudF9pZCA9IGNhc2Vfd2hlbigKICAgICAgbmV0d29yayA9PSAicmVldmFsdWF0ZWQiIH4gIkQyYiIsCiAgICAgIG1lYXN1cmVtZW50X2lkID09IDEgfiAiRDEiLAogICAgICBtZWFzdXJlbWVudF9pZCA9PSAyIH4gIkQxYiIsCiAgICAgIG1lYXN1cmVtZW50X2lkID09IDMgfiAiRDIiCiAgICApLAogICAgc2hvcnRlc3RfcGF0aCA9IGZhY3RvcihzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMpCiAgKSAlPiUKICBzZWxlY3QoCiAgICBzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc2hvcnRlc3RfcGF0aCwKICAgIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLAogICAgb3B0MV9pZCwgb3B0Ml9pZCwKICAgIGNvcnJlY3RfY2hvaWNlLCBzdWJfY2hvaWNlLAogICAgY29ycmVjdCwgcnQsCiAgICB0d29fY29ycmVjdF9vcHRpb25zLAogICAgb3B0MV9kaXN0YW5jZSA9IGRpc3Rfb3B0MSwKICAgIG9wdDJfZGlzdGFuY2UgPSBkaXN0X29wdDIKICApCmBgYAoKYGBge3IgbG9hZC1kYXRhLWZvci1wcGN9CmJmc19iYWNrd2FyZF9zaW1zIDwtIGhlcmUoCiAgImRhdGEiLCAiYmZzX3NpbXMiLCAiYmZzX3NpbXNfbGVhcm5lZF9iYWNrd2FyZC5jc3YiCikgJT4lCiAgcmVhZF9jc3Yoc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkgJT4lCiAgZmlsdGVyKAogICAgc2hvcnRlc3RfcGF0aF9naXZlbl9vcHRzID09IHNob3J0ZXN0X3BhdGhfZ2l2ZW5fc3RhcnRfZW5kLAogICAgIyB0d29fY29ycmVjdF9vcHRpb25zID09IEZBTFNFCiAgKSAlPiUKICBtdXRhdGUoc2hvcnRlc3RfcGF0aCA9IGZhY3RvcihzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMpKSAlPiUKICBzZWxlY3QoLXN0YXJ0c193aXRoKCJzaG9ydGVzdF9wYXRoX2dpdmVuIikpICU+JQogIGdyb3VwX2J5KAogICAgc2hvcnRlc3RfcGF0aCwgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQsIG9wdDJfaWQsCiAgICBjb3JyZWN0X2Nob2ljZSwgdHdvX2NvcnJlY3Rfb3B0aW9ucwogICkgJT4lCiAgc3VtbWFyaXNlKAogICAgcF9iZnNfY29ycmVjdCA9IG1lYW4oYmZzX2Nob2ljZSA9PSBjb3JyZWN0X2Nob2ljZSksCiAgICBwX2Jmc19jaG9vc2VzX29wdDEgPSBtZWFuKGJmc19jaG9pY2UgPT0gb3B0MV9pZCksCiAgICBiZnNfdmlzaXRzID0gbWVhbihiZnNfbl92aXNpdHNfdG90YWwpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkKCmJmc19mb3J3YXJkX3NpbXMgPC0gaGVyZSgiZGF0YSIsICJiZnNfc2ltcyIsICJiZnNfc2ltc19sZWFybmVkX2ZvcndhcmQuY3N2IikgJT4lCiAgcmVhZF9jc3Yoc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkgJT4lCiAgZmlsdGVyKAogICAgc2hvcnRlc3RfcGF0aF9naXZlbl9vcHRzID09IHNob3J0ZXN0X3BhdGhfZ2l2ZW5fc3RhcnRfZW5kLAogICAgIyB0d29fY29ycmVjdF9vcHRpb25zID09IEZBTFNFCiAgKSAlPiUKICBtdXRhdGUoc2hvcnRlc3RfcGF0aCA9IGZhY3RvcihzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMpKSAlPiUKICBzZWxlY3QoLXN0YXJ0c193aXRoKCJzaG9ydGVzdF9wYXRoX2dpdmVuIikpICU+JQogIGdyb3VwX2J5KAogICAgc2hvcnRlc3RfcGF0aCwgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQsIG9wdDJfaWQsCiAgICBjb3JyZWN0X2Nob2ljZSwgdHdvX2NvcnJlY3Rfb3B0aW9ucwogICkgJT4lCiAgc3VtbWFyaXNlKAogICAgcF9iZnNfY29ycmVjdCA9IG1lYW4oYmZzX2Nob2ljZSA9PSBjb3JyZWN0X2Nob2ljZSksCiAgICBwX2Jmc19jaG9vc2VzX29wdDEgPSBtZWFuKGJmc19jaG9pY2UgPT0gb3B0MV9pZCksCiAgICBiZnNfdmlzaXRzID0gbWVhbihiZnNfbl92aXNpdHNfdG90YWwpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkKCm5hdl90cmlhbHMgPC0gaGVyZSgiZGF0YSIsICJjbGVhbl9kYXRhIiwgInN0dWR5MV9tZXNzYWdlX3Bhc3NpbmcuY3N2IikgJT4lCiAgcmVhZF9jc3Yoc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkgJT4lCiAgZmlsdGVyKAogICAgIyB0d29fY29ycmVjdF9vcHRpb25zID09IEZBTFNFLAogICAgc2hvcnRlc3RfcGF0aF9naXZlbl9vcHRzID09IHNob3J0ZXN0X3BhdGhfZ2l2ZW5fc3RhcnRfZW5kCiAgKSAlPiUKICBtdXRhdGUoc2hvcnRlc3RfcGF0aCA9IGZhY3RvcihzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMpKSAlPiUKICBmaWx0ZXIoc3ViX2lkID09IDEpICU+JQogIHNlbGVjdCgKICAgIHNob3J0ZXN0X3BhdGgsCiAgICBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwKICAgIG9wdDFfaWQsIG9wdDJfaWQsCiAgICBjb3JyZWN0X2Nob2ljZSwKICAgIG9wdDFfZGlzdGFuY2UgPSBkaXN0X29wdDEsCiAgICBvcHQyX2Rpc3RhbmNlID0gZGlzdF9vcHQyLAogICAgdHdvX2NvcnJlY3Rfb3B0aW9ucwogICkgJT4lCiAgYXJyYW5nZShzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZCkgJT4lCiAgIyBSZXBsYWNlIHVuZGVmaW5lZCBkaXN0YW5jZXMgKGNvcnJlc3BvbmRpbmcgdG8gaW1wb3NzaWJsZSBvcHRpb25zKQogICMgc28gdGhhdCB0aGUgc29mdG1heCBnZXRzIG5vbi1OQSBpbnB1dHM7IHdlIGFzc3VtZSB0aGF0IGltcG9zc2libGUKICAjIG9wdGlvbnMgYXJlIGp1c3QgYXMgYmFkIGFzIHRoZSBsb25nZXN0IGRpc3RhbmNlIGZvdW5kIGluIHRoaXMgc2V0CiAgIyBvZiB0cmlhbHMsIGkuZS4sIGEgZGlzdGFuY2Ugb2YgOAogIG11dGF0ZShhY3Jvc3MoYyhvcHQxX2Rpc3RhbmNlLCBvcHQyX2Rpc3RhbmNlKSwgfnJlcGxhY2VfbmEoLngsIDgpKSkKCmFkamxpc3QgPC0gaGVyZSgiZGF0YSIsICJjbGVhbl9kYXRhIiwgImFkamxpc3RfbGVhcm5lZC5jc3YiKSAlPiUKICByZWFkX2NzdihzaG93X2NvbF90eXBlcyA9IEZBTFNFKQoKdHJhbnNtYXQgPC0gYWRqbGlzdCAlPiUKICBncm91cF9ieShmcm9tKSAlPiUKICBtdXRhdGUoZWRnZSA9IGVkZ2UgLyBzdW0oZWRnZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdG8sIHZhbHVlc19mcm9tID0gZWRnZSkgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJmcm9tIikgJT4lCiAgYXMubWF0cml4KCkKYGBgCgpgYGB7ciBsb2FkLXBhcmFtc30KbG9hZF9wYXJhbXNfZnJvbV9zY3JhdGNoIDwtIEZBTFNFCgppZiAobG9hZF9wYXJhbXNfZnJvbV9zY3JhdGNoID09IFRSVUUpIHsKICBwYXJhbXMgPC0gaGVyZSgiZGF0YSIsICJwYXJhbV9maXRzIikgJT4lCiAgICBmczo6ZGlyX2xzKAogICAgICByZWN1cnNlID0gMSwKICAgICAgcmVnZXhwID0gc3RyX2MoCiAgICAgICAgIl8iLAogICAgICAgICIoYmZzXyhiYWNrd2FyZHxmb3J3YXJkKXwiLAogICAgICAgICJpZGVhbF9vYnN8IiwKICAgICAgICAic3JfKGFuYWx5dGljfGRlbHRhX3J1bGUpKV8iLAogICAgICAgICIoLikrXFwuY3N2IgogICAgICApCiAgICApICU+JQogICAgbWFwX2RmcigKICAgICAgLmYgPSB+cmVhZF9jc3YoLngsIHNob3dfY29sX3R5cGVzID0gRkFMU0UpLAogICAgICAuaWQgPSAiZmlsZW5hbWUiCiAgICApICU+JQogICAgbXV0YXRlKAogICAgICAjIFJlY292ZXIgbW9kZWwgSUQKICAgICAgbW9kZWwgPSBzdHJfZXh0cmFjdCgKICAgICAgICBmaWxlbmFtZSwKICAgICAgICBzdHJfYygKICAgICAgICAgICJfIiwKICAgICAgICAgICIoYmZzXyhiYWNrd2FyZHxmb3J3YXJkKXwiLAogICAgICAgICAgImlkZWFsX29ic3wiLAogICAgICAgICAgInNyXyhhbmFseXRpY3xkZWx0YV9ydWxlKSlfIgogICAgICAgICkKICAgICAgKSwKICAgICAgbW9kZWwgPSBzdHJfc3ViKG1vZGVsLCAyLCAtMiksCiAgICAgICMgUmVjb3ZlciBzdHVkeSBJRAogICAgICBzdHVkeSA9IHN0cl9leHRyYWN0KGZpbGVuYW1lLCAic3R1ZHlbWzpkaWdpdDpdXSIpLAogICAgICBzdHVkeSA9IHN0cl9yZXBsYWNlKHN0dWR5LCAic3R1ZHkiLCAiU3R1ZHkgIiksCiAgICAgICMgUmVjb3ZlciBzdWJqZWN0IElECiAgICAgIHN1Yl9pZCA9IHN0cl9leHRyYWN0KGZpbGVuYW1lLCAic3ViX1tbOmRpZ2l0Ol1dKyIpLAogICAgICBzdWJfaWQgPSBzdHJfcmVtb3ZlKHN1Yl9pZCwgInN1Yl8iKSwKICAgICAgc3ViX2lkID0gYXMuaW50ZWdlcihzdWJfaWQpLAogICAgICAjIFJlY292ZXIgbWVhc3VyZW1lbnQgSUQKICAgICAgbWVhc3VyZW1lbnRfaWQgPSBzdHJfZXh0cmFjdChmaWxlbmFtZSwgIl9EW1s6ZGlnaXQ6XV1iPyIpLAogICAgICBtZWFzdXJlbWVudF9pZCA9IHN0cl9yZW1vdmUobWVhc3VyZW1lbnRfaWQsICJfIiksCiAgICAgICMgR2V0IHBhcmFtZXRlciB2YWx1ZXMKICAgICAgcGFyYW1fdmFsdWUgPSBpZl9lbHNlKAogICAgICAgIGlzLm5hKHBhcmFtX3ZhbHVlX2h1bWFuX3JlYWRhYmxlKSwKICAgICAgICBwYXJhbV92YWx1ZSwKICAgICAgICBwYXJhbV92YWx1ZV9odW1hbl9yZWFkYWJsZQogICAgICApCiAgICApICU+JQogICAgIyBGaW5kIGJlc3QtZml0dGluZyBvcHRpbWl6YXRpb24gcnVuCiAgICBmaWx0ZXIoY29udmVyZ2VuY2UgPT0gImNvbnZlcmdlZCIpICU+JQogICAgZ3JvdXBfYnkobW9kZWwsIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkKSAlPiUKICAgIHNsaWNlX21pbihvcHRpbV92YWx1ZSwgbiA9IDEpICU+JQogICAgdW5ncm91cCgpICU+JQogICAgIyBTb21lIHN1YmplY3RzIG1heSBoYXZlIGhhZCBtdWx0aXBsZSAiYmVzdCIgb3B0aW1pemF0aW9uIHJ1bnMKICAgICMgSW4gdGhhdCBjYXNlLCBqdXN0IGdvIHdpdGggd2hpY2hldmVyICJiZXN0IiBydW4gd2FzIGVzdGltYXRlZCBmaXJzdAogICAgZ3JvdXBfYnkobW9kZWwsIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkKSAlPiUKICAgIHNsaWNlX21pbihvcHRpbWl6ZXJfcnVuLCBuID0gMSkgJT4lCiAgICB1bmdyb3VwKCkgJT4lCiAgICAjIENsZWFuIHVwCiAgICBzZWxlY3QoCiAgICAgIG1vZGVsLCBzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwKICAgICAgcGFyYW1fbmFtZSwgcGFyYW1fdmFsdWUsCiAgICAgIG5lZ19sb2dsaWsgPSBvcHRpbV92YWx1ZQogICAgKSAlPiUKICAgIGFycmFuZ2UobW9kZWwsIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLCBwYXJhbV9uYW1lKQogIAogIGhlcmUoImRhdGEiLCAicGFyYW1fZml0cyIsICJjbGVhbl9wYXJhbXMiKSAlPiUKICAgIGNyZWF0ZV9wYXRoKCkKICAKICBwYXJhbXMgJT4lCiAgICB3cml0ZV9jc3YoCiAgICAgIGZpbGUgPSBoZXJlKCJkYXRhIiwgInBhcmFtX2ZpdHMiLCAiY2xlYW5fcGFyYW1zIiwgImNsZWFuX3BhcmFtX2ZpdHMuY3N2IikKICAgICkKfQoKcGFyYW1zIDwtIGhlcmUoImRhdGEiLCAicGFyYW1fZml0cyIsICJjbGVhbl9wYXJhbXMiLCAiY2xlYW5fcGFyYW1fZml0cy5jc3YiKSAlPiUKICByZWFkX2NzdihzaG93X2NvbF90eXBlcyA9IEZBTFNFKQpgYGAKCgojIEFJQ2MKCkFzIG91ciBtZXRyaWMgb2YgbG9nLWV2aWRlbmNlLCB3ZSdsbCB1c2UgQUlDYywgaS5lLiwgQUlDIGNvcnJlY3RlZCBmb3IgYSByZWxhdGl2ZWx5IHNtYWxsIE4uCgpgYGB7ciBjYWxjLWFpY2N9CmFpY2MgPC0gcGFyYW1zICU+JQogIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgbW9kZWwsIG5lZ19sb2dsaWspICU+JQogIGRpc3RpbmN0KCkgJT4lCiAgYXJyYW5nZShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCkgJT4lCiAgbXV0YXRlKAogICAgbl9wYXJhbXMgPSBpZl9lbHNlKG1vZGVsID09ICJzciIsIDIsIDEpLAogICAgbl9kYXRhcG9pbnRzID0gMTE1LAogICAgYWljID0gKC0yICogLW5lZ19sb2dsaWspICsgKDIgKiBuX3BhcmFtcyksCiAgICBhaWNjID0gYWljICsgKAogICAgICAoMiAqIG5fcGFyYW1zICogKG5fcGFyYW1zICsgMSkpIC8gKG5fZGF0YXBvaW50cyAtIG5fcGFyYW1zIC0gMSkKICAgICkKICApCmBgYAoKCiMgU1IgYW5hbHl0aWMgdnMgZGVsdGEtcnVsZQoKQmVmb3JlIHdlIGdvIGFueSBmdXJ0aGVyLCBsZXQncyBqdXN0IGdldCBvbmUgdGFyZ2V0ZWQgY29tcGFyaXNvbiBvdXQgb2YgdGhlIHdheS4gSW4gZWFybGllciBzY3JpcHRzLCB3ZSBzYXcgdGhhdCBTUiBtYXRyaWNlcyBjYW4gYmUgY29uc3RydWN0ZWQgdXNpbmcgYSBjbG9zZWQtZm9ybSBhbmFseXRpYyBzb2x1dGlvbiwgb3IgYSBkZWx0YS1ydWxlIHVwZGF0aW5nIG1lY2hhbmlzbS4gVGhlc2UgZGlmZmVyZW50IGltcGxlbWVudGF0aW9ucyBjYW4sIGluIHByaW5jaXBsZSwgZW5kIHVwIG1ha2luZyB2ZXJ5IGRpZmZlcmVudCBwcmVkaWN0aW9ucy4gSGVyZSwgd2UnbGwgc2VlIHdoZXRoZXIgdGhlcmUncyBldmlkZW5jZSB0aGF0IG9uZSBpbXBsZW1lbnRhdGlvbiBmaXRzIGJldHRlciB0aGFuIHRoZSBvdGhlci4KCkJlbG93LCB3ZSdyZSBkaXJlY3RseSBjb21wYXJpbmcgdGhlIEFJQ2Mgb2YgdGhlIGFuYWx5dGljIHZzIGRlbHRhLXJ1bGUgaW1wbGVtZW50YXRpb25zLiBFYWNoIGRhdGFwb2ludCBpcyBvbmUgc3ViamVjdCwgYW5kIHRoZSBsaW5lcyBjb25uZWN0IGEgc3ViamVjdCdzIEFJQ2MgZnJvbSBvbmUgaW1wbGVtZW50YXRpb24gdG8gdGhlIG90aGVyLiBUaGUgcmVkIGJhcnMgcmVmbGVjdCB0aGUgbWVhbnMuIFRoZSBsaW5lcyBhcmUgcmVtYXJrYWJseSBmbGF0LCBpbmRpY2F0aW5nIHRoYXQgdGhlcmUgaXMgZnVuY3Rpb25hbGx5IG5vIHJlYWwgZGlmZmVyZW5jZSBpbiB0aGUgbW9kZWwgZ29vZG5lc3Mtb2YtZml0LgoKYGBge3Igc3ItY29tcGFyaXNvbi1haWNjfQpwbG90X3NyX2NvbXBhcmlzb25fYWljYyA8LSBhaWNjICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KG1vZGVsLCAic3JfIikpICU+JQogIG11dGF0ZShmYWNldF9sYWJlbCA9IHN0cl9jKHN0dWR5LCAiLCAiLCBtZWFzdXJlbWVudF9pZCkpICU+JQogIGdncGxvdChhZXMoeD1tb2RlbCwgeT1haWNjKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5mYWNldF9sYWJlbCwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IHN1Yl9pZCksIGFscGhhID0gMC4yKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiY3Jvc3NiYXIiLCBmdW4gPSBtZWFuLCBjb2xvciA9ICJyZWQiKSArCiAgc2NhbGVfeF9kaXNjcmV0ZSgKICAgIG5hbWUgPSAiU1IgaW1wbGVtZW50YXRpb24iLAogICAgbGFiZWxzID0gYygic3JfYW5hbHl0aWMiPSJBbmFseXRpYyIsICJzcl9kZWx0YV9ydWxlIj0iRGVsdGEtcnVsZSIpCiAgKSArCiAgeWxhYigiQUlDYyIpICsKICBnZ3RpdGxlKCJBSUNjIGNvbXBhcmlzb24gb2YgU1IgaW1wbGVtZW50YXRpb25zIikKCnBsb3Rfc3JfY29tcGFyaXNvbl9haWNjCgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoCiAgICAgICJvdXRwdXRzIiwgd29ya2Zsb3dfbmFtZSwKICAgICAgInNyX2FpY2NfYW5hbHl0aWNfdnNfZGVsdGFfcnVsZS5wZGYiCiAgICApLAogICAgcGxvdCA9IHBsb3Rfc3JfY29tcGFyaXNvbl9haWNjLAogICAgd2lkdGggPSA2LCBoZWlnaHQgPSA0LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCn0KYGBgCgpXZSdyZSBub3QgaW50ZXJlc3RlZCBpbiBkb2luZyBhbnkgc29ydCBvZiBoeXBvdGhlc2lzIHRlc3RpbmcgaGVyZSwgYnV0IGZvciB0aGUgcHVycG9zZSBvZiBkb2luZyBtb2RlbCBzZWxlY3Rpb24sIHdlIGRvIHdhbnQgdG8ga25vdyB3aGV0aGVyIGNob29zaW5nIG9uZSBpbXBsZW1lbnRhdGlvbiBvdmVyIHRoZSBvdGhlciBtaWdodCByZXN1bHQgaW4gZm9ybWluZyBkaWZmZXJlbnQgY29uY2x1c2lvbnMgYWJvdXQgdGhlIHBhcmFtZXRlciBmaXRzLgoKQmVsb3csIHRoZSBiYXJzIHJlZmxlY3QgbWVkaWFucy4gV2UgY2FuIHNlZSB0aGF0LCBieS1hbmQtbGFyZ2UsIHRoZSB0d28gaW1wbGVtZW50YXRpb25zIHJlc3VsdCBpbiB2ZXJ5IHNpbWlsYXIgZXN0aW1hdGVzLgoKYGBge3Igc3ItY29tcGFyaXNvbi1nYW1tYX0KcGxvdF9zcl9jb21wYXJpc29uX2dhbW1hIDwtIHBhcmFtcyAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChtb2RlbCwgInNyXyIpKSAlPiUKICBmaWx0ZXIocGFyYW1fbmFtZSA9PSAic3JfZ2FtbWEiKSAlPiUKICBzZWxlY3QobW9kZWwsIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLCBzcl9nYW1tYSA9IHBhcmFtX3ZhbHVlKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bWVhc3VyZW1lbnRfaWQsIHk9c3JfZ2FtbWEsIGNvbG9yPW1vZGVsKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zdHVkeSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX3BvaW50KAogICAgYWxwaGEgPSAwLjUsIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjI1KSwgc2hvdy5sZWdlbmQgPSBGQUxTRQogICkgKwogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBpbnRlcmFjdGlvbihtb2RlbCwgc3ViX2lkKSksIGFscGhhID0gMC4yKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiY3Jvc3NiYXIiLCBmdW4gPSBtZWRpYW4pICsKICB4bGFiKCJNZWFzdXJlbWVudCIpICsKICB5bGFiKCJTUiBnYW1tYSIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoCiAgICBuYW1lID0gIlNSIGltcGxlbWVudGF0aW9uIiwKICAgIGxhYmVscyA9IGMoInNyX2FuYWx5dGljIj0iQW5hbHl0aWMiLCAic3JfZGVsdGFfcnVsZSI9IkRlbHRhLXJ1bGUiKSwKICAgIHZhbHVlcyA9IGMoInNyX2FuYWx5dGljIj0iI2NhMDAyMCIsICJzcl9kZWx0YV9ydWxlIj0iIzA1NzFiMCIpCiAgKSArCiAgZ2d0aXRsZSgiRXN0aW1hdGVzIG9mIFNSIGdhbW1hOiBBbmFseXRpYyB2cyBkZWx0YS1ydWxlIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQoKcGxvdF9zcl9jb21wYXJpc29uX2dhbW1hCgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoCiAgICAgICJvdXRwdXRzIiwgd29ya2Zsb3dfbmFtZSwKICAgICAgInNyX2dhbW1hX2FuYWx5dGljX3ZzX2RlbHRhX3J1bGUucGRmIgogICAgKSwKICAgIHBsb3QgPSBwbG90X3NyX2NvbXBhcmlzb25fZ2FtbWEsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDQsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKfQpgYGAKCkluIHRoZSBtb2RlbGluZywgdGhlIGdvYWwgd2FzIHRvIHRlc3QgY2hhbmdlcyBpbiB0aGUgZ2FtbWEgcGFyYW1ldGVyLCBhc3N1bWluZyBhbiBhc3ltcHRvdGljIHJlcHJlc2VudGF0aW9uLiBUaGVzZSByZXN1bHRzIHN1Z2dlc3QgdGhhdCB0aGVyZSBpcyBub3RoaW5nIGxvc3QgYnkgdXNpbmcgdGhlIGFuYWx5dGljIGNsb3NlZC1mb3JtIGltcGxlbWVudGF0aW9uLCBzbyB3ZSdsbCBzdGljayB3aXRoIHRoYXQgZnJvbSBoZXJlIG9ud2FyZHMuCgoKIyBEZXNjcmlwdGlvbiBvZiBwYXJhbWV0ZXIgZml0cwoKYGBge3IgZGVzY3JpcHRpdmUtcGFyYW0tZml0c30KcGFyYW1zICU+JQogIGdyb3VwX2J5KG1vZGVsLCBzdHVkeSwgbWVhc3VyZW1lbnRfaWQsIHBhcmFtX25hbWUpICU+JQogIHN1bW1hcmlzZSgKICAgIHBhcmFtX21lYW4gPSBtZWFuKHBhcmFtX3ZhbHVlKSwKICAgIHBhcmFtX21lZGlhbiA9IG1lZGlhbihwYXJhbV92YWx1ZSksCiAgICAuZ3JvdXBzID0gImRyb3AiCiAgKSAlPiUKICBrYWJsZV9jdXN0b20oCiAgICBjYXB0aW9ucyA9ICJEZXNjcmlwdGl2ZSBzdGF0czogcGFyYW1ldGVyIGZpdHMiLAogICAgZ3JvdXBpbmdfdmFyID0gbW9kZWwKICApCmBgYAoKYGBge3IgcGxvdC1wYXJhbS1maXRzfQpwbG90X3BhcmFtc19iZnNfYmFja3dhcmQgPC0gcGFyYW1zICU+JQogIGZpbHRlcihtb2RlbCA9PSAiYmZzX2JhY2t3YXJkIikgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHBhcmFtX25hbWUsIHZhbHVlc19mcm9tID0gcGFyYW1fdmFsdWUpICU+JQogIGdncGxvdChhZXMoeD1tZWFzdXJlbWVudF9pZCwgeT1zZWFyY2hfdGhyZXNob2xkKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zdHVkeSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lYW4sIGNvbG9yID0gInJlZCIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lZGlhbiwgY29sb3IgPSAiYmx1ZSIpICsKICBnZW9tX3BvaW50KAogICAgYWxwaGEgPSAwLjEsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMSwgaGVpZ2h0ID0gMCwgc2VlZCA9IDEpCiAgKSArCiAgZ2VvbV9saW5lKAogICAgYWVzKGdyb3VwID0gc3ViX2lkKSwgYWxwaGEgPSAwLjI1LAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjEsIGhlaWdodCA9IDAsIHNlZWQgPSAxKQogICkgKwogIHhsYWIoIk1lYXN1cmVtZW50IikgKwogIHlsYWIoIlNlYXJjaCB0aHJlc2hvbGQiKSArCiAgZ2d0aXRsZSgiQkZTLWJhY2t3YXJkIikKCnBsb3RfcGFyYW1zX2Jmc19mb3J3YXJkIDwtIHBhcmFtcyAlPiUKICBmaWx0ZXIobW9kZWwgPT0gImJmc19mb3J3YXJkIikgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHBhcmFtX25hbWUsIHZhbHVlc19mcm9tID0gcGFyYW1fdmFsdWUpICU+JQogIGdncGxvdChhZXMoeD1tZWFzdXJlbWVudF9pZCwgeT1zZWFyY2hfdGhyZXNob2xkKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zdHVkeSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lYW4sIGNvbG9yID0gInJlZCIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lZGlhbiwgY29sb3IgPSAiYmx1ZSIpICsKICBnZW9tX3BvaW50KAogICAgYWxwaGEgPSAwLjEsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMSwgaGVpZ2h0ID0gMCwgc2VlZCA9IDEpCiAgKSArCiAgZ2VvbV9saW5lKAogICAgYWVzKGdyb3VwID0gc3ViX2lkKSwgYWxwaGEgPSAwLjI1LAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjEsIGhlaWdodCA9IDAsIHNlZWQgPSAxKQogICkgKwogIHhsYWIoIk1lYXN1cmVtZW50IikgKwogIHlsYWIoIlNlYXJjaCB0aHJlc2hvbGQiKSArCiAgZ2d0aXRsZSgiQkZTLWZvcndhcmQiKQoKcGxvdF9wYXJhbXNfaWRlYWxfb2JzIDwtIHBhcmFtcyAlPiUKICBmaWx0ZXIobW9kZWwgPT0gImlkZWFsX29icyIpICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBwYXJhbV9uYW1lLCB2YWx1ZXNfZnJvbSA9IHBhcmFtX3ZhbHVlKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bWVhc3VyZW1lbnRfaWQsIHk9c29mdG1heF90ZW1wZXJhdHVyZSkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfd3JhcCh+c3R1ZHksIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiY3Jvc3NiYXIiLCBmdW4gPSBtZWFuLCBjb2xvciA9ICJyZWQiKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiY3Jvc3NiYXIiLCBmdW4gPSBtZWRpYW4sIGNvbG9yID0gImJsdWUiKSArCiAgZ2VvbV9wb2ludCgKICAgIGFscGhhID0gMC4xLAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjEsIGhlaWdodCA9IDAsIHNlZWQgPSAxKQogICkgKwogIGdlb21fbGluZSgKICAgIGFlcyhncm91cCA9IHN1Yl9pZCksIGFscGhhID0gMC4yNSwKICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xLCBoZWlnaHQgPSAwLCBzZWVkID0gMSkKICApICsKICB4bGFiKCJNZWFzdXJlbWVudCIpICsKICB5bGFiKCJJbnZlcnNlIHRlbXBlcmF0dXJlIikgKwogIGdndGl0bGUoIklkZWFsIG9ic2VydmVyIikKCnBsb3RfcGFyYW1zX3NyX3RlbXAgPC0gcGFyYW1zICU+JQogIGZpbHRlcihtb2RlbCA9PSAic3JfYW5hbHl0aWMiKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gcGFyYW1fbmFtZSwgdmFsdWVzX2Zyb20gPSBwYXJhbV92YWx1ZSkgJT4lCiAgZ2dwbG90KGFlcyh4PW1lYXN1cmVtZW50X2lkLCB5PXNvZnRtYXhfdGVtcGVyYXR1cmUpKSArCiAgdGhlbWVfY3VzdG9tKCkgKwogIGZhY2V0X3dyYXAofnN0dWR5LCBzY2FsZXMgPSAiZnJlZV94IikgKwogIHN0YXRfc3VtbWFyeShnZW9tID0gImNyb3NzYmFyIiwgZnVuID0gbWVhbiwgY29sb3IgPSAicmVkIikgKwogIHN0YXRfc3VtbWFyeShnZW9tID0gImNyb3NzYmFyIiwgZnVuID0gbWVkaWFuLCBjb2xvciA9ICJibHVlIikgKwogIGdlb21fcG9pbnQoCiAgICBhbHBoYSA9IDAuMSwKICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xLCBoZWlnaHQgPSAwLCBzZWVkID0gMSkKICApICsKICBnZW9tX2xpbmUoCiAgICBhZXMoZ3JvdXAgPSBzdWJfaWQpLCBhbHBoYSA9IDAuMjUsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMSwgaGVpZ2h0ID0gMCwgc2VlZCA9IDEpCiAgKSArCiAgeGxhYigiTWVhc3VyZW1lbnQiKSArCiAgeWxhYigiSW52ZXJzZSB0ZW1wZXJhdHVyZSIpICsKICBnZ3RpdGxlKCJTdWNjZXNzb3IgUmVwcmVzZW50YXRpb24iKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKDAsIDMwMDApKQoKcGxvdF9wYXJhbXNfc3JfZ2FtbWEgPC0gcGFyYW1zICU+JQogIGZpbHRlcihtb2RlbCA9PSAic3JfYW5hbHl0aWMiKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gcGFyYW1fbmFtZSwgdmFsdWVzX2Zyb20gPSBwYXJhbV92YWx1ZSkgJT4lCiAgZ2dwbG90KGFlcyh4PW1lYXN1cmVtZW50X2lkLCB5PXNyX2dhbW1hKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zdHVkeSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lYW4sIGNvbG9yID0gInJlZCIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lZGlhbiwgY29sb3IgPSAiYmx1ZSIpICsKICBnZW9tX3BvaW50KAogICAgYWxwaGEgPSAwLjEsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMSwgaGVpZ2h0ID0gMCwgc2VlZCA9IDEpCiAgKSArCiAgZ2VvbV9saW5lKAogICAgYWVzKGdyb3VwID0gc3ViX2lkKSwgYWxwaGEgPSAwLjI1LAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjEsIGhlaWdodCA9IDAsIHNlZWQgPSAxKQogICkgKwogIHhsYWIoIk1lYXN1cmVtZW50IikgKwogIHlsYWIoIkdhbW1hIikgKwogIGdndGl0bGUoIlN1Y2Nlc3NvciBSZXByZXNlbnRhdGlvbiIpCgpwbG90X3BhcmFtc19hbGwgPC0gKAogIChwbG90X3BhcmFtc19iZnNfYmFja3dhcmQgfCBwbG90X3BhcmFtc19iZnNfZm9yd2FyZCB8IHBsb3RfcGFyYW1zX2lkZWFsX29icykgLwogICAgKHBsb3RfcGFyYW1zX3NyX3RlbXAgfCBwbG90X3BhcmFtc19zcl9nYW1tYSkKKSArCiAgcGxvdF9hbm5vdGF0aW9uKAogICAgdGl0bGUgPSAiRXN0aW1hdGVkIHBhcmFtZXRlcnMiLAogICAgdGFnX2xldmVscyA9ICJBIiwgdGFnX3N1ZmZpeCA9ICIuIiwKICAgIHRoZW1lID0gdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCiAgKQoKcGxvdF9wYXJhbXNfYWxsCgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAicGFyYW1fZXN0aW1hdGVzLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfcGFyYW1zX2FsbCwKICAgIHdpZHRoID0gMTIsIGhlaWdodCA9IDYsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKfQpgYGAKCgojIEFrYWlrZSB3ZWlnaHRzCgpXZSdsbCBsYXRlciB1c2UgcHJvdGVjdGVkIGV4Y2VlZGFuY2UgcHJvYmFiaWxpdGllcyAoUFhQKSB0byBkbyBmb3JtYWwgaW5mZXJlbmNlIHRvIHRlc3Qgd2hldGhlciBhIHBhcnRpY3VsYXIgbW9kZWwgcHJvdmlkZXMgYSBzaWduaWZpY2FudGx5IGJldHRlciBncm91cC1sZXZlbCBmaXQgdGhhbiBvdGhlciBtb2RlbHMuIEJ1dCBmaXJzdCwgd2UgZG8gd2FudCB0byBhY2tub3dsZWRnZSB0aGF0IHRoZXJlJ3MgbGlrZWx5IHRvIGJlIHNvbWUgaW5kaXZpZHVhbCBkaWZmZXJlbmNlcyBpbiBob3cgd2VsbCBhIHBhcnRpY3VsYXIgbW9kZWwgZml0cyBlYWNoIHN1YmplY3QuIFRvIGdldCBhIHNlbnNlIGZvciB0aGlzLCB3ZSdsbCB1c2UgQWthaWtlIHdlaWdodHMsIHdoaWNoIHByb3ZpZGUgdGhlIHByb2JhYmlsaXR5IHRoYXQgYSBwYXJ0aWN1bGFyIG1vZGVsIGlzIHRoZSAiYmVzdCIgZ2l2ZW4gdGhlIGRhdGEgYW5kIHRoZSBzZXQgb2YgY2FuZGlkYXRlIG1vZGVscy4KCmBgYHtyIGNhbGMtYWthaWtlLXdlaWdodHN9CmFrYWlrZV93ZWlnaHRzIDwtIGFpY2MgJT4lCiAgZmlsdGVyKG1vZGVsICE9ICJzcl9kZWx0YV9ydWxlIikgJT4lCiAgZ3JvdXBfYnkoc3R1ZHksIG1lYXN1cmVtZW50X2lkLCBzdWJfaWQpICU+JQogIG11dGF0ZSgKICAgIHJlbGF0aXZlX2xpa2VsaWhvb2QgPSBleHAoLTEvMiAqIChhaWNjIC0gbWluKGFpY2MpKSksCiAgICBha2Fpa2Vfd2VpZ2h0ID0gcmVsYXRpdmVfbGlrZWxpaG9vZCAvIHN1bShyZWxhdGl2ZV9saWtlbGlob29kKSwKICAgIGV2aWRlbmNlX3JhdGlvID0gbWF4KGFrYWlrZV93ZWlnaHQpIC8gYWthaWtlX3dlaWdodAogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIGFycmFuZ2Uoc3R1ZHksIG1lYXN1cmVtZW50X2lkLCBzdWJfaWQsIGV2aWRlbmNlX3JhdGlvKQpgYGAKCldlIGNhbiBmaXJzdCBhdmVyYWdlIG92ZXIgYWxsIHN1YmplY3RzJyBBa2Fpa2Ugd2VpZ2h0cyB0byBnZXQgYSBzZW5zZSBmb3Igd2hhdCB0aGUgImJlc3QtZml0dGluZyIgbW9kZWwgaXMgYWNyb3NzIHN1YmplY3RzLiBUaGlzIHN1Z2dlc3RzIHRoYXQgdGhlIFNSIGNvbnNpc3RlbnRseSBjb21lcyBvdXQgb24gdG9wLCBmb2xsb3dlZCBwcmV0dHkgY29uc2lzdGVudGx5IGJ5IEJGUy1iYWNrd2FyZC4KCmBgYHtyIHBsb3QtZ3JvdXAtYWthaWtlLXdlaWdodHN9CnBsb3RfYWthaWtlX2dyb3VwIDwtIGFrYWlrZV93ZWlnaHRzICU+JQogIGdyb3VwX2J5KHN0dWR5LCBtZWFzdXJlbWVudF9pZCwgbW9kZWwpICU+JQogIHN1bW1hcmlzZShha2Fpa2Vfd2VpZ2h0ID0gbWVhbihha2Fpa2Vfd2VpZ2h0KSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgbXV0YXRlKHRleHQgPSByb3VuZChha2Fpa2Vfd2VpZ2h0LCAyKSkgJT4lCiAgZ2dwbG90KGFlcyh4PW1lYXN1cmVtZW50X2lkLCB5PWFrYWlrZV93ZWlnaHQsIGZpbGw9bW9kZWwpKSArCiAgdGhlbWVfY3VzdG9tKCkgKwogIGZhY2V0X3dyYXAofnN0dWR5LCBzY2FsZXMgPSAiZnJlZV94IikgKwogIGdlb21fY29sKCkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSB0ZXh0KSwgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSkpICsKICBzY2FsZV94X2Rpc2NyZXRlKAogICAgbmFtZSA9IE5VTEwsCiAgICBsYWJlbHMgPSBjKAogICAgICAiRDEiPSJCZWZvcmVcbnJlc3QiLAogICAgICAiRDIiPSJBZnRlclxub3Zlcm5pZ2h0XG5yZXN0IiwKICAgICAgIkQxYiI9IkFmdGVyXG5hd2FrZVxucmVzdCIKICAgICkKICApICsKICBzY2FsZV95X2NvbnRpbnVvdXMoCiAgICBuYW1lID0gTlVMTCwKICAgIGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLjAxLCAwLjAxKSkKICApICsKICBzY2FsZV9maWxsX21hbnVhbCgKICAgIG5hbWUgPSBOVUxMLAogICAgbGFiZWxzID0gYygKICAgICAgImJmc19iYWNrd2FyZCIgPSAiQkZTLWJhY2t3YXJkIiwKICAgICAgImJmc19mb3J3YXJkIiA9ICJCRlMtZm9yd2FyZCIsCiAgICAgICJpZGVhbF9vYnMiID0gIklkZWFsIG9ic2VydmVyIiwKICAgICAgInNyX2FuYWx5dGljIiA9ICJTdWNjZXNzb3IgUmVwcmVzZW50YXRpb24iCiAgICApLAogICAgdmFsdWVzID0gYygKICAgICAgImJmc19iYWNrd2FyZCIgPSAiI2E2ZGJhMCIsCiAgICAgICJiZnNfZm9yd2FyZCIgPSAiIzVhYWU2MSIsCiAgICAgICJpZGVhbF9vYnMiID0gIiMxYjc4MzciLAogICAgICAic3JfYW5hbHl0aWMiID0gIiNhZjhkYzMiCiAgICApCiAgKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpCiAgKSArCiAgZ2d0aXRsZSgiQWthaWtlIHdlaWdodHMiKQoKcGxvdF9ha2Fpa2VfZ3JvdXAKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJha2Fpa2Vfd2VpZ2h0c19ncm91cC5wZGYiKSwKICAgIHBsb3QgPSBwbG90X2FrYWlrZV9ncm91cCwKICAgIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKV2UgY2FuIGJyZWFrIHRoaXMgb3V0IGFuZCBwbG90IGVhY2ggaW5kaXZpZHVhbCBzdWJqZWN0J3MgQWthaWtlIHdlaWdodHMuCgpgYGB7ciBwbG90LWluZGl2aWR1YWwtYWthaWtlLXdlaWdodHN9CnBsb3RfYWthaWtlX2luZGl2aWR1YWwgPC0gYWthaWtlX3dlaWdodHMgJT4lCiAgbXV0YXRlKAogICAgc3ViX2lkID0gZmFjdG9yKHN1Yl9pZCksCiAgICBtZWFzdXJlbWVudF9pZCA9IGNhc2Vfd2hlbigKICAgICAgbWVhc3VyZW1lbnRfaWQgPT0gIkQxIiB+ICJiZWZvcmUgcmVzdCIsCiAgICAgIG1lYXN1cmVtZW50X2lkID09ICJEMWIiIH4gImFmdGVyIGF3YWtlIHJlc3QiLAogICAgICBtZWFzdXJlbWVudF9pZCA9PSAiRDIiIH4gImFmdGVyIG92ZXJuaWdodCByZXN0IgogICAgKSwKICAgIHN0dWR5ID0gc3RyX2Moc3R1ZHksICIsICIsIG1lYXN1cmVtZW50X2lkKSwKICAgIHN0dWR5ID0gZmN0X3JlbGV2ZWwoCiAgICAgIHN0dWR5LAogICAgICAiU3R1ZHkgMSwgYmVmb3JlIHJlc3QiLAogICAgICAiU3R1ZHkgMiwgYmVmb3JlIHJlc3QiLAogICAgICAiU3R1ZHkgMiwgYWZ0ZXIgb3Zlcm5pZ2h0IHJlc3QiLAogICAgICAiU3R1ZHkgMywgYmVmb3JlIHJlc3QiLAogICAgICAiU3R1ZHkgMywgYWZ0ZXIgYXdha2UgcmVzdCIsCiAgICAgICJTdHVkeSAzLCBhZnRlciBvdmVybmlnaHQgcmVzdCIKICAgICkKICApICU+JQogIGdncGxvdChhZXMoeD1zdWJfaWQsIHk9YWthaWtlX3dlaWdodCwgZmlsbD1tb2RlbCkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfd3JhcCh+c3R1ZHksIHNjYWxlcyA9ICJmcmVlX3giLCBuY29sID0gMSkgKwogIGdlb21fY29sKCkgKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJTdWJqZWN0IElEIikgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIG5hbWUgPSBOVUxMLAogICAgZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAuMDEsIDAuMDEpKQogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKAogICAgbmFtZSA9IE5VTEwsCiAgICBsYWJlbHMgPSBjKAogICAgICAiYmZzX2JhY2t3YXJkIiA9ICJCRlMtYmFja3dhcmQiLAogICAgICAiYmZzX2ZvcndhcmQiID0gIkJGUy1mb3J3YXJkIiwKICAgICAgImlkZWFsX29icyIgPSAiSWRlYWwgb2JzZXJ2ZXIiLAogICAgICAic3JfYW5hbHl0aWMiID0gIlN1Y2Nlc3NvciBSZXByZXNlbnRhdGlvbiIKICAgICksCiAgICB2YWx1ZXMgPSBjKAogICAgICAiYmZzX2JhY2t3YXJkIiA9ICIjYTZkYmEwIiwKICAgICAgImJmc19mb3J3YXJkIiA9ICIjNWFhZTYxIiwKICAgICAgImlkZWFsX29icyIgPSAiIzFiNzgzNyIsCiAgICAgICJzcl9hbmFseXRpYyIgPSAiI2FmOGRjMyIKICAgICkKICApICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0ID0gMSksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKQogICkgKwogIGdndGl0bGUoIkFrYWlrZSB3ZWlnaHRzIikKCnBsb3RfYWthaWtlX2luZGl2aWR1YWwKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgKICAgICAgIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLAogICAgICAiYWthaWtlX3dlaWdodHNfcGVyX3N1YmplY3QucGRmIgogICAgKSwKICAgIHBsb3QgPSBwbG90X2FrYWlrZV9pbmRpdmlkdWFsLAogICAgd2lkdGggPSA4LCBoZWlnaHQgPSAxMCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKQWthaWtlIHdlaWdodHMgcHJvdmlkZSBhIG5pY2UgZ29vZG5lc3Mtb2YtZml0IG1ldHJpYyB0aGF0IHJlc3BlY3RzIHRoZSBwcm9iYWJpbGlzdGljIGFzcGVjdCBvZiBtb2RlbCBjb21wYXJpc29uLCBhbmQgY2FuIGRvIHNvIGF0IGJvdGggdGhlIGdyb3VwLSBhbmQgaW5kaXZpZHVhbC1sZXZlbC4gSG93ZXZlciwgbW9kZWwgc2VsZWN0aW9uIHJlcXVpcmVzIHVzIHRvIHVsdGltYXRlbHkgbWFrZSBhIGRpc2NyZXRlIGNob2ljZS4gSWYgd2UgbWFkZSBwZXItc3ViamVjdCBkZWNpc2lvbnMgYmFzZWQgc2ltcGx5IGJ5IGNob29zaW5nIHRoZSBzaW5nbGUgYmVzdC1maXR0aW5nIG1vZGVsLCB3aGF0IHByb3BvcnRpb24gb2Ygc3ViamVjdHMgYXJlIGJlc3QtZml0IGJ5IGVhY2ggbW9kZWw/IFdlIHNlZSB0aGF0IHRoZSBwYXR0ZXJuIG9mIHJlc3VsdHMgYmFzaWNhbGx5IG1pcnJvcnMgd2hhdCB3ZSdkIHNlZW4gaW4gdGhlIEFrYWlrZSB3ZWlnaHRzLCBzdWNoIHRoYXQgdGhlIFNSIGlzIHRoZSBiZXN0LWZpdHRpbmcgbW9kZWwgZm9yIHRoZSBtYWpvcml0eSBvZiBzdWJqZWN0cywgZm9sbG93ZWQgYnkgQkZTLWJhY2t3YXJkLgoKYGBge3IgcGxvdC1ncm91cC1wcm9wLWJlc3QtZml0c30KYmVzdF9maXR0aW5nX21vZGVsX3Blcl9zdWIgPC0gYWthaWtlX3dlaWdodHMgJT4lCiAgZ3JvdXBfYnkoc3R1ZHksIG1lYXN1cmVtZW50X2lkLCBzdWJfaWQpICU+JQogIHNsaWNlX21heChha2Fpa2Vfd2VpZ2h0KSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgc2VsZWN0KHN0dWR5LCBtZWFzdXJlbWVudF9pZCwgc3ViX2lkLCBiZXN0X2ZpdHRpbmdfbW9kZWwgPSBtb2RlbCkKCnBsb3RfYmVzdF9maXR0aW5nX21vZGVsX3Byb3AgPC0gYmVzdF9maXR0aW5nX21vZGVsX3Blcl9zdWIgJT4lCiAgY291bnQoc3R1ZHksIG1lYXN1cmVtZW50X2lkLCBiZXN0X2ZpdHRpbmdfbW9kZWwpICU+JQogIGdyb3VwX2J5KHN0dWR5LCBtZWFzdXJlbWVudF9pZCkgJT4lCiAgbXV0YXRlKAogICAgcCA9IG4gLyBzdW0obiksCiAgICB0ZXh0ID0gc3RyX2Mocm91bmQocCwgMikgKiAxMDAsICIlIikKICApICU+JQogIGdncGxvdChhZXMoeD1tZWFzdXJlbWVudF9pZCwgeT1wLCBmaWxsPWJlc3RfZml0dGluZ19tb2RlbCkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfd3JhcCh+c3R1ZHksIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgZ2VvbV9jb2woKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHRleHQpLCBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSkgKwogIHNjYWxlX3hfZGlzY3JldGUoCiAgICBuYW1lID0gTlVMTCwKICAgIGxhYmVscyA9IGMoCiAgICAgICJEMSI9IkJlZm9yZVxucmVzdCIsCiAgICAgICJEMiI9IkFmdGVyXG5vdmVybmlnaHRcbnJlc3QiLAogICAgICAiRDFiIj0iQWZ0ZXJcbmF3YWtlXG5yZXN0IgogICAgKQogICkgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIG5hbWUgPSBOVUxMLAogICAgZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAuMDEsIDAuMDEpKQogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKAogICAgbmFtZSA9IE5VTEwsCiAgICBsYWJlbHMgPSBjKAogICAgICAiYmZzX2JhY2t3YXJkIiA9ICJCRlMtYmFja3dhcmQiLAogICAgICAiYmZzX2ZvcndhcmQiID0gIkJGUy1mb3J3YXJkIiwKICAgICAgImlkZWFsX29icyIgPSAiSWRlYWwgb2JzZXJ2ZXIiLAogICAgICAic3JfYW5hbHl0aWMiID0gIlN1Y2Nlc3NvciBSZXByZXNlbnRhdGlvbiIKICAgICksCiAgICB2YWx1ZXMgPSBjKAogICAgICAiYmZzX2JhY2t3YXJkIiA9ICIjYTZkYmEwIiwKICAgICAgImJmc19mb3J3YXJkIiA9ICIjNWFhZTYxIiwKICAgICAgImlkZWFsX29icyIgPSAiIzFiNzgzNyIsCiAgICAgICJzcl9hbmFseXRpYyIgPSAiI2FmOGRjMyIKICAgICkKICApICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCkKICApICsKICBnZ3RpdGxlKCJQcm9wb3J0aW9uIG9mIHN1YmplY3RzIGJlc3QgZml0IGJ5IGVhY2ggbW9kZWwiKQoKcGxvdF9iZXN0X2ZpdHRpbmdfbW9kZWxfcHJvcAoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKAogICAgICAib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsCiAgICAgICJwcm9wX3N1YmplY3RzX2Jlc3RfZml0LnBkZiIKICAgICksCiAgICBwbG90ID0gcGxvdF9iZXN0X2ZpdHRpbmdfbW9kZWxfcHJvcCwKICAgIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKCiMgUHJvdGVjdGVkIGV4Y2VlZGFuY2UgcHJvYmFiaWxpdGllcwoKUHJvdGVjdGVkIGV4Y2VlZGFuY2UgcHJvYmFiaWxpdGllcyBwcm92aWRlIGEgZm9ybWFsIHRlc3Qgb2YgYSBtb2RlbOKAmXMgZ3JvdXAtbGV2ZWwgZml0IGNvbXBhcmVkIHRvIG90aGVyIGNhbmRpZGF0ZSBtb2RlbHMuIFRvIHJ1biB0aGlzIGFuYWx5c2lzLCB3ZSdsbCBhZGFwdCBzb2Z0d2FyZSBvcmlnaW5hbGx5IHdyaXR0ZW4gYnkgTWF0dGVvIExpc2kgKGh0dHBzOi8vZ2l0aHViLmNvbS9tYXR0ZWxpc2kvYm1zUikuIFdlJ2xsIHVzZSBBSUNjIGFzIG91ciBtZXRyaWMgb2YgbG9nLWV2aWRlbmNlLgoKYGBge3IgY2FsYy1weHB9CnB4cF9yZXN1bHRzIDwtIGFpY2MgJT4lCiAgZmlsdGVyKG1vZGVsICE9ICJzcl9kZWx0YV9ydWxlIikgJT4lCiAgIyBJbiBQWFAsIG1vcmUgaXMgbW9yZS4gQUlDYywgaW4gY29udHJhc3QsIGlzIGJhc2VkIG9mZiBuZWctTEwsIGFuZCBzbyBpcwogICMgaW50ZXJwcmV0ZWQgYXMgInNtYWxsZXIgaXMgYmV0dGVyIi4gU28sIGRvIGEgc2lnbiBmbGlwLgogIG11dGF0ZShhaWNjID0gLWFpY2MpICU+JQogIHNlbGVjdChzdHVkeSwgbWVhc3VyZW1lbnRfaWQsIHN1Yl9pZCwgbW9kZWwsIGFpY2MpICU+JQogICMgQ29tcHV0ZSBQWFAgZm9yIGVhY2ggc3R1ZHkvbWVhc3VyZW1lbnQKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gbW9kZWwsIHZhbHVlc19mcm9tID0gYWljYykgJT4lCiAgc2VsZWN0KC1zdWJfaWQpICU+JQogIGdyb3VwX2J5KHN0dWR5LCBtZWFzdXJlbWVudF9pZCkgJT4lCiAgbmVzdCgpICU+JQogIG11dGF0ZSgKICAgIHRlc3QgPSBtYXAoCiAgICAgIC54ID0gZGF0YSwKICAgICAgLmYgPSB+YmF5ZXNpYW5fbW9kZWxfc2VsZWN0aW9uKC54KQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIHVubmVzdCh0ZXN0KSAlPiUKICBzZWxlY3QoLWRhdGEpCmBgYAoKSW4gdGhlIHJlc3VsdHMsIGl0J3MgY2xlYXIgdGhhdCB0aGUgU1IgY29tZXMgb3V0IG9uIHRvcCwgYW5kIGJ5IGEgbGFyZ2UgbWFyZ2luLgoKYGBge3IgcHhwLXJlc3VsdHN9CnB4cF9yZXN1bHRzICU+JQogIG11dGF0ZSgKICAgIG1lYXN1cmVtZW50X2lkID0gY2FzZV93aGVuKAogICAgICBtZWFzdXJlbWVudF9pZCA9PSAiRDEiIH4gImJlZm9yZSByZXN0IiwKICAgICAgbWVhc3VyZW1lbnRfaWQgPT0gIkQxYiIgfiAiYWZ0ZXIgYXdha2UgcmVzdCIsCiAgICAgIG1lYXN1cmVtZW50X2lkID09ICJEMiIgfiAiYWZ0ZXIgb3Zlcm5pZ2h0IHJlc3QiCiAgICApLAogICAgc3R1ZHkgPSBzdHJfYyhzdHVkeSwgIiwgIiwgbWVhc3VyZW1lbnRfaWQpLAogICAgc3R1ZHkgPSBmY3RfcmVsZXZlbCgKICAgICAgc3R1ZHksCiAgICAgICJTdHVkeSAxLCBiZWZvcmUgcmVzdCIsCiAgICAgICJTdHVkeSAyLCBiZWZvcmUgcmVzdCIsCiAgICAgICJTdHVkeSAyLCBhZnRlciBvdmVybmlnaHQgcmVzdCIsCiAgICAgICJTdHVkeSAzLCBiZWZvcmUgcmVzdCIsCiAgICAgICJTdHVkeSAzLCBhZnRlciBhd2FrZSByZXN0IiwKICAgICAgIlN0dWR5IDMsIGFmdGVyIG92ZXJuaWdodCByZXN0IgogICAgKQogICkgJT4lCiAgc2VsZWN0KC1tZWFzdXJlbWVudF9pZCkgJT4lCiAgYXJyYW5nZShzdHVkeSwgZGVzYyhweHApKSAlPiUKICBrYWJsZV9jdXN0b20oIlBYUCByZXN1bHRzIiwgZ3JvdXBpbmdfdmFyID0gc3R1ZHkpCmBgYAoKYGBge3IgcGxvdC1weHB9CnBsb3RfcHhwIDwtIHB4cF9yZXN1bHRzICU+JQogIG11dGF0ZSgKICAgIHRleHQgPSByb3VuZChweHAsIDIpICogMTAwLAogICAgdGV4dCA9IGlmX2Vsc2UobW9kZWwgIT0gInNyX2FuYWx5dGljIiwgIiIsIHN0cl9jKHRleHQsICIlIikpCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bWVhc3VyZW1lbnRfaWQsIHk9cHhwLCBmaWxsPW1vZGVsKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zdHVkeSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX2NvbCgpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gdGV4dCksIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpKSArCiAgc2NhbGVfeF9kaXNjcmV0ZSgKICAgIG5hbWUgPSBOVUxMLAogICAgbGFiZWxzID0gYygKICAgICAgIkQxIj0iQmVmb3JlXG5yZXN0IiwKICAgICAgIkQyIj0iQWZ0ZXJcbm92ZXJuaWdodFxucmVzdCIsCiAgICAgICJEMWIiPSJBZnRlclxuYXdha2VcbnJlc3QiCiAgICApCiAgKSArCiAgc2NhbGVfeV9jb250aW51b3VzKAogICAgbmFtZSA9IE5VTEwsCiAgICBleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMC4wMSwgMC4wMSkpCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwoCiAgICBuYW1lID0gTlVMTCwKICAgIGxhYmVscyA9IGMoCiAgICAgICJiZnNfYmFja3dhcmQiID0gIkJGUy1iYWNrd2FyZCIsCiAgICAgICJiZnNfZm9yd2FyZCIgPSAiQkZTLWZvcndhcmQiLAogICAgICAiaWRlYWxfb2JzIiA9ICJJZGVhbCBvYnNlcnZlciIsCiAgICAgICJzcl9hbmFseXRpYyIgPSAiU3VjY2Vzc29yIFJlcHJlc2VudGF0aW9uIgogICAgKSwKICAgIHZhbHVlcyA9IGMoCiAgICAgICJiZnNfYmFja3dhcmQiID0gIiNhNmRiYTAiLAogICAgICAiYmZzX2ZvcndhcmQiID0gIiM1YWFlNjEiLAogICAgICAiaWRlYWxfb2JzIiA9ICIjMWI3ODM3IiwKICAgICAgInNyX2FuYWx5dGljIiA9ICIjYWY4ZGMzIgogICAgKQogICkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKQogICkgKwogIGdndGl0bGUoIlByb3RlY3RlZCBleGNlZWRhbmNlIHByb2JhYmlsaXRpZXMiKQoKcGxvdF9weHAKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgKICAgICAgIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLAogICAgICAicHhwLnBkZiIKICAgICksCiAgICBwbG90ID0gcGxvdF9weHAsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDQsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKfQpgYGAKCgojIFBvc3RlcmlvciBwcmVkaWN0aXZlIGNoZWNrCgojIyBTaW11bGF0ZSBwcmVkaWN0ZWQgYmVoYXZpb3JzCgpJdCdzIG5pY2UgdG8gc2VlIGNvbnNpc3RlbmN5IGluIHRoZSAqcXVhbnRpdGF0aXZlKiBtb2RlbCBjb21wYXJpc29uIGFuZCBzZWxlY3Rpb24sIGJ1dCB3ZSdkIGFsc28gbGlrZSB0byBzZWUgaG93IHdlbGwgaHVtYW4gYmVoYXZpb3JzIGFyZSAqcXVhbGl0YXRpdmVseSogZGVzY3JpYmVkIGJ5IG91ciBtb2RlbHMuIFRvIGRvIHRoaXMsIHdlJ2xsIHNpbXVsYXRlIHN1YmplY3RzJyBwcmVkaWN0ZWQgYmVoYXZpb3JzIGdpdmVuIHRoZWlyIG1vZGVsIHBhcmFtZXRlcnMuCgpgYGB7ciBwcGMtYmZzLWJhY2t3YXJkfQpwcGNfYmZzX2JhY2t3YXJkIDwtIGV4cGFuZF9ncmlkKAogICMgTGlzdCBvZiBhbGwgdHJpYWxzIGZyb20gQkZTIHNpbXVsYXRpb24gZm9yIGVhY2ggc3ViamVjdC9tZWFzdXJlbWVudAogIHBhcmFtcyAlPiUKICAgIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCkgJT4lCiAgICBkaXN0aW5jdCgpLAogIGJmc19iYWNrd2FyZF9zaW1zCikgJT4lCiAgIyBBZGQgc3ViamVjdC1zcGVjaWZpYyBwYXJhbWV0ZXJzCiAgbGVmdF9qb2luKAogICAgcGFyYW1zICU+JQogICAgICBmaWx0ZXIobW9kZWwgPT0gImJmc19iYWNrd2FyZCIpICU+JQogICAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gcGFyYW1fbmFtZSwgdmFsdWVzX2Zyb20gPSBwYXJhbV92YWx1ZSkgJT4lCiAgICAgIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc2VhcmNoX3RocmVzaG9sZCksCiAgICBieSA9IGpvaW5fYnkoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQpCiAgKSAlPiUKICAjIFdoYXQncyB0aGUgcHJvYmFiaWxpdHkgb2YgKmNvbXBsZXRpbmcqIEJGUy1vbmxpbmUgYWxsIHRoZSB3YXkgdGhyb3VnaD8KICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcF9jb21wbGV0ZV9iZnMgPSBzb2Z0bWF4KAogICAgICBvcHRpb25fdmFsdWVzID0gYyhzZWFyY2hfdGhyZXNob2xkLCBiZnNfdmlzaXRzKSwKICAgICAgb3B0aW9uX2Nob3NlbiA9IDEsCiAgICAgIHRlbXBlcmF0dXJlID0gMQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogICMgV2VpZ2ggQkZTIHByZWRpY3Rpb25zIGFjY29yZGluZ2x5CiAgbXV0YXRlKAogICAgcF9naXZlX3VwID0gMSAtIHBfY29tcGxldGVfYmZzLAogICAgbW9kZWxfcF9jb3JyZWN0ID0gKAogICAgICAocF9jb21wbGV0ZV9iZnMgKiBwX2Jmc19jb3JyZWN0KSArIChwX2dpdmVfdXAgKiAxLzIpCiAgICApCiAgKSAlPiUKICAjIEFkZCBzdWJqZWN0cycgYWN0dWFsIGNob2ljZXMKICBsZWZ0X2pvaW4oCiAgICBiaW5kX3Jvd3MobmF2X3N0dWR5MSwgbmF2X3N0dWR5MiwgbmF2X3N0dWR5MykgJT4lCiAgICAgIHNlbGVjdCgKICAgICAgICBzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwKICAgICAgICBzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZCwKICAgICAgICBzdWJfY2hvaWNlLCBzdWJfY29ycmVjdCA9IGNvcnJlY3QsIHN1Yl9ydCA9IHJ0CiAgICAgICksCiAgICBieSA9IGpvaW5fYnkoCiAgICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgICBzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZAogICAgKQogICkKYGBgCgpgYGB7ciBwcGMtYmZzLWZvcndhcmR9CnBwY19iZnNfZm9yd2FyZCA8LSBleHBhbmRfZ3JpZCgKICAjIExpc3Qgb2YgYWxsIHRyaWFscyBmcm9tIEJGUyBzaW11bGF0aW9uIGZvciBlYWNoIHN1YmplY3QvbWVhc3VyZW1lbnQKICBwYXJhbXMgJT4lCiAgICBzZWxlY3Qoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQpICU+JQogICAgZGlzdGluY3QoKSwKICBiZnNfZm9yd2FyZF9zaW1zCikgJT4lCiAgIyBBZGQgc3ViamVjdC1zcGVjaWZpYyBwYXJhbWV0ZXJzCiAgbGVmdF9qb2luKAogICAgcGFyYW1zICU+JQogICAgICBmaWx0ZXIobW9kZWwgPT0gImJmc19mb3J3YXJkIikgJT4lCiAgICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBwYXJhbV9uYW1lLCB2YWx1ZXNfZnJvbSA9IHBhcmFtX3ZhbHVlKSAlPiUKICAgICAgc2VsZWN0KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLCBzZWFyY2hfdGhyZXNob2xkKSwKICAgIGJ5ID0gam9pbl9ieShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCkKICApICU+JQogICMgV2hhdCdzIHRoZSBwcm9iYWJpbGl0eSBvZiAqY29tcGxldGluZyogQkZTLW9ubGluZSBhbGwgdGhlIHdheSB0aHJvdWdoPwogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoCiAgICBwX2NvbXBsZXRlX2JmcyA9IHNvZnRtYXgoCiAgICAgIG9wdGlvbl92YWx1ZXMgPSBjKHNlYXJjaF90aHJlc2hvbGQsIGJmc192aXNpdHMpLAogICAgICBvcHRpb25fY2hvc2VuID0gMSwKICAgICAgdGVtcGVyYXR1cmUgPSAxCiAgICApCiAgKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgIyBXZWlnaCBCRlMgcHJlZGljdGlvbnMgYWNjb3JkaW5nbHkKICBtdXRhdGUoCiAgICBwX2dpdmVfdXAgPSAxIC0gcF9jb21wbGV0ZV9iZnMsCiAgICBtb2RlbF9wX2NvcnJlY3QgPSAoCiAgICAgIChwX2NvbXBsZXRlX2JmcyAqIHBfYmZzX2NvcnJlY3QpICsgKHBfZ2l2ZV91cCAqIDEvMikKICAgICkKICApICU+JQogICMgQWRkIHN1YmplY3RzJyBhY3R1YWwgY2hvaWNlcwogIGxlZnRfam9pbigKICAgIGJpbmRfcm93cyhuYXZfc3R1ZHkxLCBuYXZfc3R1ZHkyLCBuYXZfc3R1ZHkzKSAlPiUKICAgICAgc2VsZWN0KAogICAgICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkLAogICAgICAgIHN1Yl9jaG9pY2UsIHN1Yl9jb3JyZWN0ID0gY29ycmVjdCwgc3ViX3J0ID0gcnQKICAgICAgKSwKICAgIGJ5ID0gam9pbl9ieSgKICAgICAgc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsCiAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkCiAgICApCiAgKQpgYGAKCmBgYHtyIHBwYy1pZGVhbC1vYnN9CnBwY19pZGVhbF9vYnMgPC0gZXhwYW5kX2dyaWQoCiAgIyBMaXN0IG9mIGFsbCB0cmlhbHMgZm9yIGVhY2ggc3ViamVjdC9tZWFzdXJlbWVudAogIHBhcmFtcyAlPiUKICAgIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCkgJT4lCiAgICBkaXN0aW5jdCgpLAogIG5hdl90cmlhbHMKKSAlPiUKICAjIEFkZCBzdWJqZWN0LXNwZWNpZmljIHBhcmFtZXRlcnMKICBsZWZ0X2pvaW4oCiAgICBwYXJhbXMgJT4lCiAgICAgIGZpbHRlcihtb2RlbCA9PSAiaWRlYWxfb2JzIikgJT4lCiAgICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBwYXJhbV9uYW1lLCB2YWx1ZXNfZnJvbSA9IHBhcmFtX3ZhbHVlKSAlPiUKICAgICAgc2VsZWN0KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLCBzb2Z0bWF4X3RlbXBlcmF0dXJlKSwKICAgIGJ5ID0gam9pbl9ieShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCkKICApICU+JQogICMgTW9kZWwgcHJlZGljdGlvbnMKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgbW9kZWxfcF9jb3JyZWN0ID0gc29mdG1heCgKICAgICAgb3B0aW9uX3ZhbHVlcyA9IGMob3B0MV9kaXN0YW5jZSwgb3B0Ml9kaXN0YW5jZSksCiAgICAgIG9wdGlvbl9jaG9zZW4gPSBpZl9lbHNlKGNvcnJlY3RfY2hvaWNlID09IG9wdDFfaWQsIDEsIDIpLAogICAgICB0ZW1wZXJhdHVyZSA9IHNvZnRtYXhfdGVtcGVyYXR1cmUsCiAgICAgIHVzZV9pbnZlcnNlX3RlbXBlcmF0dXJlID0gVFJVRQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogICMgQWRkIHN1YmplY3RzJyBhY3R1YWwgY2hvaWNlcwogIGxlZnRfam9pbigKICAgIGJpbmRfcm93cyhuYXZfc3R1ZHkxLCBuYXZfc3R1ZHkyLCBuYXZfc3R1ZHkzKSAlPiUKICAgICAgc2VsZWN0KAogICAgICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkLAogICAgICAgIHN1Yl9jaG9pY2UsIHN1Yl9jb3JyZWN0ID0gY29ycmVjdCwgc3ViX3J0ID0gcnQKICAgICAgKSwKICAgIGJ5ID0gam9pbl9ieSgKICAgICAgc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsCiAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkCiAgICApCiAgKQpgYGAKCmBgYHtyIHBwYy1zcn0KcHBjX3NyX3JlcHJlc2VudGF0aW9uIDwtIHBhcmFtcyAlPiUKICBmaWx0ZXIobW9kZWwgPT0gInNyX2FuYWx5dGljIikgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHBhcmFtX25hbWUsIHZhbHVlc19mcm9tID0gcGFyYW1fdmFsdWUpICU+JQogIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc3JfZ2FtbWEpICU+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoCiAgICBwcmVkaWN0ZWRfc3IgPSBtYXAoCiAgICAgIC54ID0gc3JfZ2FtbWEsCiAgICAgIC5mID0gfmJ1aWxkX3N1Y2Nlc3Nvcl9hbmFseXRpY2FsbHkoCiAgICAgICAgdHJhbnNtYXQsIHN1Y2Nlc3Nvcl9ob3Jpem9uID0gLngsIG5vcm1hbGl6ZSA9IFRSVUUKICAgICAgKQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgcHJlZGljdGVkX3NyKSAlPiUKICB1bm5lc3QocHJlZGljdGVkX3NyKQoKcHBjX3NyX25hdmlnYXRpb24gPC0gZXhwYW5kX2dyaWQoCiAgIyBMaXN0IG9mIGFsbCB0cmlhbHMgZm9yIGVhY2ggc3ViamVjdC9tZWFzdXJlbWVudAogIHBhcmFtcyAlPiUKICAgIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCkgJT4lCiAgICBkaXN0aW5jdCgpLAogIG5hdl90cmlhbHMKKSAlPiUKICAjIEFkZCBzdWJqZWN0LXNwZWNpZmljIHBhcmFtZXRlcnMKICBsZWZ0X2pvaW4oCiAgICBwYXJhbXMgJT4lCiAgICAgIGZpbHRlcihtb2RlbCA9PSAic3JfYW5hbHl0aWMiKSAlPiUKICAgICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHBhcmFtX25hbWUsIHZhbHVlc19mcm9tID0gcGFyYW1fdmFsdWUpICU+JQogICAgICBzZWxlY3Qoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsIHNvZnRtYXhfdGVtcGVyYXR1cmUpLAogICAgYnkgPSBqb2luX2J5KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkKQogICkgJT4lCiAgIyBBZGQgU1IgcHJlZGljdGVkIHJlcHJlc2VudGF0aW9uCiAgbGVmdF9qb2luKAogICAgcHBjX3NyX3JlcHJlc2VudGF0aW9uICU+JQogICAgICBzZWxlY3QoCiAgICAgICAgc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsCiAgICAgICAgZW5kcG9pbnRfaWQgPSB0bywgb3B0MV9pZCA9IGZyb20sIG9wdDFfc3IgPSBzcl92YWx1ZQogICAgICApLAogICAgYnkgPSBqb2luX2J5KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCkKICApICU+JQogIGxlZnRfam9pbigKICAgIHBwY19zcl9yZXByZXNlbnRhdGlvbiAlPiUKICAgICAgc2VsZWN0KAogICAgICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgICAgIGVuZHBvaW50X2lkID0gdG8sIG9wdDJfaWQgPSBmcm9tLCBvcHQyX3NyID0gc3JfdmFsdWUKICAgICAgKSwKICAgIGJ5ID0gam9pbl9ieShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgZW5kcG9pbnRfaWQsIG9wdDJfaWQpCiAgKSAlPiUKICAjIE1vZGVsIG5hdmlnYXRpb24gcHJlZGljdGlvbnMKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgbW9kZWxfcF9jb3JyZWN0ID0gc29mdG1heCgKICAgICAgb3B0aW9uX3ZhbHVlcyA9IGMob3B0MV9zciwgb3B0Ml9zciksCiAgICAgIG9wdGlvbl9jaG9zZW4gPSBpZl9lbHNlKGNvcnJlY3RfY2hvaWNlID09IG9wdDFfaWQsIDEsIDIpLAogICAgICB0ZW1wZXJhdHVyZSA9IHNvZnRtYXhfdGVtcGVyYXR1cmUsCiAgICAgIHVzZV9pbnZlcnNlX3RlbXBlcmF0dXJlID0gVFJVRQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogICMgQWRkIHN1YmplY3RzJyBhY3R1YWwgY2hvaWNlcwogIGxlZnRfam9pbigKICAgIGJpbmRfcm93cyhuYXZfc3R1ZHkxLCBuYXZfc3R1ZHkyLCBuYXZfc3R1ZHkzKSAlPiUKICAgICAgc2VsZWN0KAogICAgICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkLAogICAgICAgIHN1Yl9jaG9pY2UsIHN1Yl9jb3JyZWN0ID0gY29ycmVjdCwgc3ViX3J0ID0gcnQKICAgICAgKSwKICAgIGJ5ID0gam9pbl9ieSgKICAgICAgc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsCiAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkCiAgICApCiAgKQpgYGAKCiMjIFBsb3QgUFBDCgpgYGB7ciBjcmVhdGUtcHBjLWZvci1wbG90dGluZ30KcHBjX2Zvcl9wbG90dGluZyA8LSBwYXJhbXMgJT4lCiAgc2VsZWN0KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkKSAlPiUKICBkaXN0aW5jdCgpICU+JQogICMgQWRkIGh1bWFuIGFjY3VyYWN5ICsgQkZTLWJhY2t3YXJkIGFjY3VyYWN5CiAgbGVmdF9qb2luKAogICAgcHBjX2Jmc19iYWNrd2FyZCAlPiUKICAgICAgZmlsdGVyKHR3b19jb3JyZWN0X29wdGlvbnMgPT0gRkFMU0UpICU+JQogICAgICBncm91cF9ieShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc2hvcnRlc3RfcGF0aCkgJT4lCiAgICAgIHN1bW1hcmlzZSgKICAgICAgICBodW1hbiA9IG1lYW4oc3ViX2NvcnJlY3QpLAogICAgICAgIGJmc19iYWNrd2FyZCA9IG1lYW4obW9kZWxfcF9jb3JyZWN0KSwKICAgICAgICAuZ3JvdXBzID0gImRyb3AiCiAgICAgICksCiAgICBieSA9IGpvaW5fYnkoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQpCiAgKSAlPiUKICAjIEFkZCBCRlMtZm9yd2FyZCBhY2N1cmFjeQogIGxlZnRfam9pbigKICAgIHBwY19iZnNfZm9yd2FyZCAlPiUKICAgICAgZmlsdGVyKHR3b19jb3JyZWN0X29wdGlvbnMgPT0gRkFMU0UpICU+JQogICAgICBncm91cF9ieShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc2hvcnRlc3RfcGF0aCkgJT4lCiAgICAgIHN1bW1hcmlzZSgKICAgICAgICBiZnNfZm9yd2FyZCA9IG1lYW4obW9kZWxfcF9jb3JyZWN0KSwKICAgICAgICAuZ3JvdXBzID0gImRyb3AiCiAgICAgICksCiAgICBieSA9IGpvaW5fYnkoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsIHNob3J0ZXN0X3BhdGgpCiAgKSAlPiUKICAjIEFkZCBpZGVhbCBvYnNlcnZlciBhY2N1cmFjeQogIGxlZnRfam9pbigKICAgIHBwY19pZGVhbF9vYnMgJT4lCiAgICAgIGZpbHRlcih0d29fY29ycmVjdF9vcHRpb25zID09IEZBTFNFKSAlPiUKICAgICAgZ3JvdXBfYnkoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsIHNob3J0ZXN0X3BhdGgpICU+JQogICAgICBzdW1tYXJpc2UoCiAgICAgICAgaWRlYWxfb2JzID0gbWVhbihtb2RlbF9wX2NvcnJlY3QpLAogICAgICAgIC5ncm91cHMgPSAiZHJvcCIKICAgICAgKSwKICAgIGJ5ID0gam9pbl9ieShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc2hvcnRlc3RfcGF0aCkKICApICU+JQogICMgQWRkIFNSIGFjY3VyYWN5CiAgbGVmdF9qb2luKAogICAgcHBjX3NyX25hdmlnYXRpb24gJT4lCiAgICAgIGZpbHRlcih0d29fY29ycmVjdF9vcHRpb25zID09IEZBTFNFKSAlPiUKICAgICAgZ3JvdXBfYnkoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsIHNob3J0ZXN0X3BhdGgpICU+JQogICAgICBzdW1tYXJpc2UoCiAgICAgICAgc3IgPSBtZWFuKG1vZGVsX3BfY29ycmVjdCksCiAgICAgICAgLmdyb3VwcyA9ICJkcm9wIgogICAgICApLAogICAgYnkgPSBqb2luX2J5KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLCBzaG9ydGVzdF9wYXRoKQogICkgJT4lCiAgIyBGb3IgcGxvdHRpbmcgYWVzdGhldGljcwogIHBpdm90X2xvbmdlcihodW1hbjpzciwgbmFtZXNfdG8gPSAiYWdlbnQiLCB2YWx1ZXNfdG8gPSAiYWNjdXJhY3kiKSAlPiUKICBtdXRhdGUoCiAgICBhZ2VudCA9IGNhc2Vfd2hlbigKICAgICAgYWdlbnQgPT0gImh1bWFuIiB+ICJIdW1hbiIsCiAgICAgIGFnZW50ID09ICJiZnNfYmFja3dhcmQiIH4gIkJGUy1iYWNrd2FyZCIsCiAgICAgIGFnZW50ID09ICJiZnNfZm9yd2FyZCIgfiAiQkZTLWZvcndhcmQiLAogICAgICBhZ2VudCA9PSAiaWRlYWxfb2JzIiB+ICJJZGVhbCBvYnNlcnZlciIsCiAgICAgIGFnZW50ID09ICJzciIgfiAiU3VjY2Vzc29yIFJlcC4iCiAgICApLAogICAgYWdlbnQgPSBmY3RfcmVsZXZlbChhZ2VudCwgIkh1bWFuIiwgIlN1Y2Nlc3NvciBSZXAuIikKICApCmBgYAoKTGV0J3MgbG9vayBhdCB0aGUgbWFpbiBzZXQgb2YgdHJpYWxzIGFuZCBjb21wYXJlIGh1bWFuIHBlcmZvcm1hbmNlIGFnYWluc3QgdGhlIG1vZGVscyBvbiBEYXkgMSAoaS5lLiwgYmVmb3JlIHJlc3QpLgoKYGBge3IgcHBjLWRheTF9CnBsb3RfcHBjX2RheTEgPC0gcHBjX2Zvcl9wbG90dGluZyAlPiUKICBmaWx0ZXIobWVhc3VyZW1lbnRfaWQgPT0gIkQxIikgJT4lCiAgZ2dwbG90KGFlcyh4PXNob3J0ZXN0X3BhdGgsIHk9YWNjdXJhY3kpKSArCiAgdGhlbWVfY3VzdG9tKCkgKwogIGZhY2V0X3dyYXAofmFnZW50LCBucm93ID0gMSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuNSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmx1ZSIpICsKICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gaW50ZXJhY3Rpb24oc3R1ZHksIHN1Yl9pZCkpLCBhbHBoYSA9IDAuMSkgKwogIHN0YXRfc3VtbWFyeShnZW9tID0gImNyb3NzYmFyIiwgZnVuID0gbWVhbiwgY29sb3IgPSAicmVkIikgKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJTaG9ydGVzdCBwYXRoIGRpc3RhbmNlIikgKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gIkFjY3VyYWN5IiwgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArCiAgZ2d0aXRsZSgiUG9zdGVyaW9yIHByZWRpY3RpdmUgY2hlY2s6IEJlZm9yZSByZXN0IikKICAKcGxvdF9wcGNfZGF5MQoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJvdXRwdXRzIiwgd29ya2Zsb3dfbmFtZSwgInBwY19kYXkxLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfcHBjX2RheTEsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDMsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKfQpgYGAKCldlJ2xsIGRvIHRoZSBzYW1lIG5vdyBmb3IgRGF5IDIgKGkuZS4sIGFmdGVyIG92ZXJuaWdodCByZXN0KS4KCmBgYHtyIHBwYy1kYXkyfQpwbG90X3BwY19kYXkyIDwtIHBwY19mb3JfcGxvdHRpbmcgJT4lCiAgZmlsdGVyKG1lYXN1cmVtZW50X2lkID09ICJEMiIpICU+JQogIGdncGxvdChhZXMoeD1zaG9ydGVzdF9wYXRoLCB5PWFjY3VyYWN5KSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5hZ2VudCwgbnJvdyA9IDEpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjUsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImJsdWUiKSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IGludGVyYWN0aW9uKHN0dWR5LCBzdWJfaWQpKSwgYWxwaGEgPSAwLjEpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lYW4sIGNvbG9yID0gInJlZCIpICsKICBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiU2hvcnRlc3QgcGF0aCBkaXN0YW5jZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJBY2N1cmFjeSIsIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKwogIGdndGl0bGUoIlBvc3RlcmlvciBwcmVkaWN0aXZlIGNoZWNrOiBBZnRlciBvdmVybmlnaHQgcmVzdCIpCiAgCnBsb3RfcHBjX2RheTIKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJwcGNfZGF5Mi5wZGYiKSwKICAgIHBsb3QgPSBwbG90X3BwY19kYXkyLAogICAgd2lkdGggPSA2LCBoZWlnaHQgPSAzLAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCn0KYGBgCgpBbmQgZmluYWxseSwgZm9yIERheSAxYiAoaS5lLiwgYWZ0ZXIgYXdha2UgcmVzdCBvbiBEYXkgMSksIGEgbWVhc3VyZW1lbnQgdGhhdCB3YXMgb25seSBtYWRlIGluIFN0dWR5IDMuCgpgYGB7ciBwcGMtZGF5MWJ9CnBsb3RfcHBjX2RheTFiIDwtIHBwY19mb3JfcGxvdHRpbmcgJT4lCiAgZmlsdGVyKG1lYXN1cmVtZW50X2lkID09ICJEMWIiKSAlPiUKICBnZ3Bsb3QoYWVzKHg9c2hvcnRlc3RfcGF0aCwgeT1hY2N1cmFjeSkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfd3JhcCh+YWdlbnQsIG5yb3cgPSAxKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJibHVlIikgKwogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBpbnRlcmFjdGlvbihzdHVkeSwgc3ViX2lkKSksIGFscGhhID0gMC4xKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiY3Jvc3NiYXIiLCBmdW4gPSBtZWFuLCBjb2xvciA9ICJyZWQiKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lID0gIlNob3J0ZXN0IHBhdGggZGlzdGFuY2UiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWUgPSAiQWNjdXJhY3kiLCBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsKICBnZ3RpdGxlKCJQb3N0ZXJpb3IgcHJlZGljdGl2ZSBjaGVjazogQWZ0ZXIgYXdha2UgcmVzdCIpCiAgCnBsb3RfcHBjX2RheTFiCgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAicHBjX2RheTFiLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfcHBjX2RheTFiLAogICAgd2lkdGggPSA2LCBoZWlnaHQgPSAzLAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCn0KYGBgCgoKIyBIZWxkLW91dCB0cmlhbHMKClRoZSBwcmltYXJ5IGFuYWx5c2VzLCBpbmNsdWRpbmcgdGhlIHBhcmFtZXRlci1maXR0aW5nLCB3ZXJlIHBlcmZvcm1lZCBvbiBhIHNldCBvZiB0cmlhbHMgd2hlcmUgdGhlcmUgd2FzIGFsd2F5cyBvbmUgdW5hbWJpZ3VvdXNseSBjb3JyZWN0IGFuc3dlci4gRm9yIHRoaXMgcmVhc29uLCB0aGVyZSB3YXMgYWxzbyBhIHN1YnNldCBvZiB0cmlhbHMgdGhhdCBnb3QgImhlbGQgb3V0IiBiZWNhdXNlIHRoZSB0d28gb3B0aW9ucyBoYWQgdGhlIHNhbWUgc2hvcnRlc3QgcGF0aCBkaXN0YW5jZSBmcm9tIHRoZSB0YXJnZXQuCgpTZXZlcmFsIG9mIHRoZSBjb21wdXRhdGlvbmFsIG1vZGVscyAoaS5lLiwgQkZTLWJhY2t3YXJkIGFuZCBpZGVhbCBvYnNlcnZlcikgdGhlcmVmb3JlIHByZWRpY3QgNTAvNTAlIGNob2ljZXMgb24gdGhlc2UgdHJpYWxzLiBCRlMtZm9yd2FyZCBkb2VzIG5vdCwgYXMgaXQgYWxsb3dzIGZvciBzdG9jaGFzdGljaXR5IGluIGhvdyBhZ2VudHMgcGVyZm9ybSBzZWFyY2hlcyBmcm9tIGVhY2ggb2YgdGhlIHR3byBvcHRpb25zLiBNb3N0IG5vdGFibHksIHRoZSBTUiBvZnRlbiBwcmVkaWN0cyB0aGF0IGFuIGFnZW50IHdpbGwgcHJlZmVyIG9uZSBvcHRpb24gb3ZlciBhbm90aGVyLCB3aGljaCBiYXNpY2FsbHkgcmVmbGVjdHMgdGhlIGZhY3QgdGhhdCAoZS5nLikgYWx0aG91Z2ggU291cmNlcyBBIGFuZCBCIGhhdmUgdGhlIHNhbWUgc2hvcnRlc3QgcGF0aCBkaXN0YW5jZSB0byB0aGUgVGFyZ2V0LCBTb3VyY2UgQSBtaWdodCBoYXZlIGEgZ3JlYXRlciBudW1iZXIgb2Ygc2hvcnQgcGF0aHMgdG8gdGhlIFRhcmdldC4KClRoZXJlZm9yZSwgaWYgc3ViamVjdHMnIGJlaGF2aW9ycyBhcmUgY29uc2lzdGVudCB3aXRoIG5vbi1yYW5kb20gcmVzcG9uZGluZywgd2UnZCBpZGVhbGx5IGxpa2UgdG8gc2VlIHRoYXQgdGhlIFNSIGlzIGFibGUgdG8gbWFrZSBtb3JlIGFjY3VyYXRlIG91dC1vZi1zYW1wbGUgcHJlZGljdGlvbnMgb24gdGhlc2UgdHJpYWxzLgoKYGBge3IgY2FsYy1oZWxkb3V0LWxpa2VsaWhvb2RzfQpoZWxkb3V0X2xpa2VsaWhvb2RzIDwtIHBwY19zcl9uYXZpZ2F0aW9uICU+JQogIGZpbHRlcih0d29fY29ycmVjdF9vcHRpb25zID09IFRSVUUpICU+JQogIHNlbGVjdCgKICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgc2hvcnRlc3RfcGF0aCwgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQsIG9wdDJfaWQsCiAgICBvcHQxX3NyLCBvcHQyX3NyLCBzdWJfY2hvaWNlLCBzb2Z0bWF4X3RlbXBlcmF0dXJlCiAgKSAlPiUKICBtdXRhdGUoCiAgICBzcl9wcmVmZXJzID0gY2FzZV93aGVuKAogICAgICBvcHQxX3NyID09IG9wdDJfc3IgfiBOQV9yZWFsXywKICAgICAgb3B0MV9zciA+IG9wdDJfc3IgfiBvcHQxX2lkLAogICAgICBUUlVFIH4gb3B0Ml9pZAogICAgKQogICkgJT4lCiAgIyBDYWxjdWxhdGUgdGhlIGxpa2VsaWhvb2Qgb2YgdGhlIHN1YmplY3QncyBjaG9pY2UsIGdpdmVuIHdoYXQgb3B0aW9uCiAgIyB0aGUgU1Igd291bGQgaGF2ZSBwcmVmZXJyZWQKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcF9zdWJfY2hvaWNlID0gc29mdG1heCgKICAgICAgb3B0aW9uX3ZhbHVlcyA9IGMob3B0MV9zciwgb3B0Ml9zciksCiAgICAgIG9wdGlvbl9jaG9zZW4gPSBpZl9lbHNlKHNyX3ByZWZlcnMgPT0gb3B0MV9pZCwgMSwgMiksCiAgICAgIHRlbXBlcmF0dXJlID0gc29mdG1heF90ZW1wZXJhdHVyZSwKICAgICAgdXNlX2ludmVyc2VfdGVtcGVyYXR1cmUgPSBUUlVFCiAgICApLAogICAgIyBGaXggYSBmZXcgZWRnZSBjYXNlcwogICAgcF9zdWJfY2hvaWNlID0gY2FzZV93aGVuKAogICAgICBpcy5uYShzcl9wcmVmZXJzKSB+IDAuNSwKICAgICAgaXMubmFuKHBfc3ViX2Nob2ljZSkgJiAoc3ViX2Nob2ljZSA9PSBzcl9wcmVmZXJzKSB+IDEsCiAgICAgICMgVG8gYXZvaWQgbG9nKDApLCB1c2UgbWFjaGluZSBlcHNpbG9uCiAgICAgIGlzLm5hbihwX3N1Yl9jaG9pY2UpICYgKHN1Yl9jaG9pY2UgIT0gc3JfcHJlZmVycykgfiAyLjIyZS0xNiwKICAgICAgVFJVRSB+IHBfc3ViX2Nob2ljZQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZShuZWdfbGxfc3IgPSBuZWdfbG9nbGlrX2xvZ2lzdGljKHBfc3ViX2Nob2ljZSkpICU+JQogICMgVGlkeSB1cCB0aGUgU1IgYml0CiAgc2VsZWN0KAogICAgc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsCiAgICBzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZCwKICAgIHNyX3ByZWZlcnMsIHN1Yl9jaG9pY2UsCiAgICBuZWdfbGxfc3IKICApICU+JQogICMgQWRkIGxpa2VsaWhvb2RzIGZvciBCRlMtYmFja3dhcmQsIHdoaWNoIGFsd2F5cyBwcmVkaWN0cyA1MC81MCByZXNwb25kaW5nCiAgbXV0YXRlKG5lZ19sbF9iZnNfYmFja3dhcmQgPSBuZWdfbG9nbGlrX2xvZ2lzdGljKDAuNSkpICU+JQogICMgQWRkIGxpa2VsaWhvb2RzIGZvciBCRlMtZm9yd2FyZAogIGxlZnRfam9pbigKICAgIHBwY19iZnNfZm9yd2FyZCAlPiUKICAgICAgc2VsZWN0KAogICAgICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkLAogICAgICAgIHBfYmZzX2Nob29zZXNfb3B0MSwgcF9jb21wbGV0ZV9iZnMsIHBfZ2l2ZV91cAogICAgICApLAogICAgYnkgPSBqb2luX2J5KAogICAgICBzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwKICAgICAgc2hvcnRlc3RfcGF0aCwgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQsIG9wdDJfaWQKICAgICkKICApICU+JQogIG11dGF0ZSgKICAgIHBfc3ViX2Nob2ljZSA9IGNhc2Vfd2hlbigKICAgICAgaXMubmEoc3JfcHJlZmVycykgfiAwLjUsCiAgICAgIHNyX3ByZWZlcnMgPT0gb3B0MV9pZCB+ICgKICAgICAgICAocF9jb21wbGV0ZV9iZnMgKiBwX2Jmc19jaG9vc2VzX29wdDEpICsgKHBfZ2l2ZV91cCAqIDEvMikKICAgICAgKSwKICAgICAgc3JfcHJlZmVycyA9PSBvcHQyX2lkIH4gKAogICAgICAgIChwX2NvbXBsZXRlX2JmcyAqICgxLXBfYmZzX2Nob29zZXNfb3B0MSkpICsgKHBfZ2l2ZV91cCAqIDEvMikKICAgICAgKQogICAgKSwKICAgIG5lZ19sbF9iZnNfZm9yd2FyZCA9IG5lZ19sb2dsaWtfbG9naXN0aWMocF9zdWJfY2hvaWNlKQogICkgJT4lCiAgIyBBZGQgbGlrZWxpaG9vZHMgZm9yIGlkZWFsIG9ic2VydmVyLCB3aGljaCBhbHdheXMgcHJlZGljdHMgNTAvNTAgcmVzcG9uZGluZwogIG11dGF0ZShuZWdfbGxfaWRlYWxfb2JzID0gbmVnX2xvZ2xpa19sb2dpc3RpYygwLjUpKSAlPiUKICAjIFRpZHkgdXAKICBzZWxlY3QoCiAgICBzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwKICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkLAogICAgc3JfcHJlZmVycywgc3ViX2Nob2ljZSwKICAgIG5lZ19sbF9zciwgbmVnX2xsX2Jmc19iYWNrd2FyZCwgbmVnX2xsX2Jmc19mb3J3YXJkLCBuZWdfbGxfaWRlYWxfb2JzCiAgKQpgYGAKCkJlbG93LCB3ZSBzZWUgdGhhdCB0aGUgU1IsIGNvbXBhcmVkIHRvIHRoZSBvdGhlciBtb2RlbHMsIGlzIGRvaW5nIGEgYmV0dGVyIGpvYiBvZiBleHBsYWluaW5nIHN1YmplY3RzJyBjaG9pY2VzIG9uIGhlbGQtb3V0IHRyaWFscywgdGhvdWdoIHdlIGFnYWluIG5vdGUgdGhhdCB0aGUgbGlrZWxpaG9vZHMgZm9yIHRoZSBCRlMtYmFja3dhcmQgYW5kIGlkZWFsIG9ic2VydmVyIG1vZGVscyBhcmUgZm9yIGNvbXBsZXRlbHkgcmFuZG9tIHJlc3BvbmRpbmcuIFRoZSBkaWZmZXJlbnQgc3R1ZGllcyBoYXZlIGRpZmZlcmVudCBiYXNlbGluZSBsb2ctbGlrZWxpaG9vZHMgYmVjYXVzZSB0aGV5IGNvbnRhaW4gYSBkaWZmZXJlbnQgbnVtYmVyIG9mIHRyaWFscyAoaS5lLiwgaW4gU3R1ZGllcyAyLTMsIHdlJ3JlIHN1bW1pbmcgb3ZlciBib3RoIERheSAxIGFuZCBEYXkgMiBtZWFzdXJlbWVudHMpLgoKYGBge3IgcGxvdC1oZWxkb3V0LWxpa2VsaWhvb2RzfQpwbG90X2hlbGRvdXRfbGlrZWxpaG9vZHMgPC0gaGVsZG91dF9saWtlbGlob29kcyAlPiUKICAjIFN1bSBzbyB0aGF0IHdlIGdldCBvbmUgbmVnLWxvZ2xpayBwZXIgc3ViamVjdAogIGdyb3VwX2J5KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkKSAlPiUKICBzdW1tYXJpc2UoYWNyb3NzKHN0YXJ0c193aXRoKCJuZWdfbGxfIiksIHN1bSksIC5ncm91cHMgPSAiZHJvcCIpICU+JQogICMgUHJlcCBmb3IgcGxvdHRpbmcKICBwaXZvdF9sb25nZXIoCiAgICBzdGFydHNfd2l0aCgibmVnX2xsXyIpLCBuYW1lc190byA9ICJtb2RlbCIsIHZhbHVlc190byA9ICJuZWdfbGwiCiAgKSAlPiUKICBtdXRhdGUobW9kZWwgPSBzdHJfcmVtb3ZlKG1vZGVsLCAibmVnX2xsXyIpLCBsb2dsaWsgPSAtbmVnX2xsKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bWVhc3VyZW1lbnRfaWQsIHk9bG9nbGlrLCBjb2xvcj1tb2RlbCkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfd3JhcCh+c3R1ZHksIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgc3RhdF9zdW1tYXJ5KAogICAgZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lZGlhbiwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuNzUpCiAgKSArCiAgZ2VvbV9wb2ludCgKICAgIGFscGhhID0gMC4yLAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXJkb2RnZSgKICAgICAgaml0dGVyLndpZHRoID0gMC4xLCBqaXR0ZXIuaGVpZ2h0ID0gMCwgZG9kZ2Uud2lkdGggPSAwLjc1LCBzZWVkID0gMQogICAgKSwKICAgIHNob3cubGVnZW5kID0gRkFMU0UKICApICsKICBzY2FsZV94X2Rpc2NyZXRlKAogICAgbmFtZSA9IE5VTEwsCiAgICBsYWJlbHMgPSBjKAogICAgICAiRDEiPSJCZWZvcmVcbnJlc3QiLAogICAgICAiRDIiPSJBZnRlclxub3Zlcm5pZ2h0XG5yZXN0IiwKICAgICAgIkQxYiI9IkFmdGVyXG5hd2FrZVxucmVzdCIKICAgICkKICApICsKICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJsb2ctbGlrZWxpaG9vZFxuKGdyZWF0ZXIgPSBiZXR0ZXIpIikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgIG5hbWUgPSBOVUxMLAogICAgbGFiZWxzID0gYygKICAgICAgImJmc19iYWNrd2FyZCIgPSAiQkZTLWJhY2t3YXJkIiwKICAgICAgImJmc19mb3J3YXJkIiA9ICJCRlMtZm9yd2FyZCIsCiAgICAgICJpZGVhbF9vYnMiID0gIklkZWFsIG9ic2VydmVyIiwKICAgICAgInNyIiA9ICJTdWNjZXNzb3IgUmVwLiIKICAgICksCiAgICB2YWx1ZXMgPSBjKAogICAgICAiYmZzX2JhY2t3YXJkIiA9ICIjYTZkYmEwIiwKICAgICAgImJmc19mb3J3YXJkIiA9ICIjNWFhZTYxIiwKICAgICAgImlkZWFsX29icyIgPSAiIzFiNzgzNyIsCiAgICAgICJzciIgPSAiI2FmOGRjMyIKICAgICkKICApICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKwogIGdndGl0bGUoIkhlbGQtb3V0IHRyaWFsczogbG9nLWxpa2VsaWhvb2RzIikKCnBsb3RfaGVsZG91dF9saWtlbGlob29kcwoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJvdXRwdXRzIiwgd29ya2Zsb3dfbmFtZSwgImhlbGRvdXRfbGlrZWxpaG9vZHMucGRmIiksCiAgICBwbG90ID0gcGxvdF9oZWxkb3V0X2xpa2VsaWhvb2RzLAogICAgd2lkdGggPSA2LCBoZWlnaHQgPSA0LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCn0KYGBgCgpBbHRob3VnaCB0aGUgbGlrZWxpaG9vZHMgZ2l2ZSB1cyBhIG5pY2UgcXVhbnRpdGF0aXZlIG1ldHJpYywgd2UgbWF5IGFsc28gYmUgaW50ZXJlc3RlZCBpbiBrbm93aW5nIGhvdyB3ZWxsIHRoZSBTUiBwcmVkaWN0cyBzdWJqZWN0cycgY2hvaWNlcyBqdXN0IGluIHRlcm1zIG9mIGFjY3VyYWN5LiBJbiB0aGUgYmVsb3cgYW5hbHlzaXMsIHdlJ2xsIHVzZSBtaXhlZC1lZmZlY3RzIGxvZ2lzdGljIHJlZ3Jlc3Npb24gdG8gdGVzdCB3aGV0aGVyIHN1YmplY3RzIHNpZ25pZmljYW50bHkgY2hvb3NlIHRoZSBTUi1wcmVmZXJyZWQgb3B0aW9uLiBOb3RlIHRoYXQgd2UncmUgcmVtb3ZpbmcgYWxsIG9mIHRoZSB0cmlhbHMgd2hlcmUgdGhlIFNSIGlzIGluZGlmZmVyZW50IHRvIHRoZSB0d28gb3B0aW9ucywgYXMgdGhvc2UgdHJpYWxzIGxlYWQgdXMgdG8gb3ZlcmVzdGltYXRlIHRoZSBtb2RlbCdzIHByZWRpY3RlZCBhY2N1cmFjeSAoaS5lLiwgYmVjYXVzZSB0aGUgc3ViamVjdCBpcyBhbHdheXMgcmlnaHQgb24gdGhvc2UgdHJpYWxzKS4gTm90ZSBhbHNvIHRoYXQgd2UncmUgaXRlcmF0aXZlbHkgcmUtcGFyYW1ldGVyaXppbmcgdGhlIG1vZGVsIHdpdGggYSBkaWZmZXJlbnQgcmVmZXJlbmNlIGNhdGVnb3J5IGVhY2ggdGltZSwgc28gdGhhdCB3ZSBjYW4gdGVzdCB3aGV0aGVyIG1vZGVsIGFjY3VyYWN5IGlzIHNpZ25pZmljYW50bHkgYWJvdmUgY2hhbmNlIGF0IGVhY2ggZGlzdGFuY2UuIEZpbmFsbHksIG5vdGUgdGhhdCBmb3IgdGhlIHB1cnBvc2Ugb2Ygc3RhdGlzdGljYWwgdGVzdGluZywgd2UncmUgbG9va2luZyBhdCB0aGUgdHdvIG1haW4gbWVhc3VyZW1lbnRzOiBiZWZvcmUgcmVzdCAoZGF5IDEpLCBhbmQgYWZ0ZXIgb3Zlcm5pZ2h0IHJlc3QgKGRheSAyKS4KCmBgYHtyIHRlc3QtaGVsZG91dC1hY2N1cmFjeX0Kc3RhdHNfaGVsZG91dF9hY2N1cmFjeV9kaXN0MiA8LSBoZWxkb3V0X2xpa2VsaWhvb2RzICU+JQogIGZpbHRlcihtZWFzdXJlbWVudF9pZCAlaW4lIGMoIkQxIiwgIkQyIikpICU+JQogIGZpbHRlcighaXMubmEoc3JfcHJlZmVycykpICU+JQogIG11dGF0ZSgKICAgIHBfc3ViX2Nob29zZXNfc3JfcHJlZmVyZW5jZSA9IHN1Yl9jaG9pY2UgPT0gc3JfcHJlZmVycywKICAgIHN1Yl9pZCA9IHN0cl9jKHN0dWR5LCAiLCBzIiwgc3ViX2lkKQogICkgJT4lCiAgZ2xtbVRNQigKICAgIHBfc3ViX2Nob29zZXNfc3JfcHJlZmVyZW5jZSB+IHNob3J0ZXN0X3BhdGggKwogICAgICAoMSB8IHN1Yl9pZCkgKyAoMSB8IHN0dWR5KSwKICAgIGZhbWlseSA9IGJpbm9taWFsLAogICAgZGF0YSA9IC4KICApCgpzdGF0c19oZWxkb3V0X2FjY3VyYWN5X2Rpc3QzIDwtIGhlbGRvdXRfbGlrZWxpaG9vZHMgJT4lCiAgZmlsdGVyKG1lYXN1cmVtZW50X2lkICVpbiUgYygiRDEiLCAiRDIiKSkgJT4lCiAgZmlsdGVyKCFpcy5uYShzcl9wcmVmZXJzKSkgJT4lCiAgbXV0YXRlKAogICAgcF9zdWJfY2hvb3Nlc19zcl9wcmVmZXJlbmNlID0gc3ViX2Nob2ljZSA9PSBzcl9wcmVmZXJzLAogICAgc3ViX2lkID0gc3RyX2Moc3R1ZHksICIsIHMiLCBzdWJfaWQpLAogICAgc2hvcnRlc3RfcGF0aCA9IGZjdF9yZWxldmVsKHNob3J0ZXN0X3BhdGgsICIzIikKICApICU+JQogIGdsbW1UTUIoCiAgICBwX3N1Yl9jaG9vc2VzX3NyX3ByZWZlcmVuY2UgfiBzaG9ydGVzdF9wYXRoICsKICAgICAgKDEgfCBzdWJfaWQpICsgKDEgfCBzdHVkeSksCiAgICBmYW1pbHkgPSBiaW5vbWlhbCwKICAgIGRhdGEgPSAuCiAgKQoKc3RhdHNfaGVsZG91dF9hY2N1cmFjeV9kaXN0NCA8LSBoZWxkb3V0X2xpa2VsaWhvb2RzICU+JQogIGZpbHRlcihtZWFzdXJlbWVudF9pZCAlaW4lIGMoIkQxIiwgIkQyIikpICU+JQogIGZpbHRlcighaXMubmEoc3JfcHJlZmVycykpICU+JQogIG11dGF0ZSgKICAgIHBfc3ViX2Nob29zZXNfc3JfcHJlZmVyZW5jZSA9IHN1Yl9jaG9pY2UgPT0gc3JfcHJlZmVycywKICAgIHN1Yl9pZCA9IHN0cl9jKHN0dWR5LCAiLCBzIiwgc3ViX2lkKSwKICAgIHNob3J0ZXN0X3BhdGggPSBmY3RfcmVsZXZlbChzaG9ydGVzdF9wYXRoLCAiNCIpCiAgKSAlPiUKICBnbG1tVE1CKAogICAgcF9zdWJfY2hvb3Nlc19zcl9wcmVmZXJlbmNlIH4gc2hvcnRlc3RfcGF0aCArCiAgICAgICgxIHwgc3ViX2lkKSArICgxIHwgc3R1ZHkpLAogICAgZmFtaWx5ID0gYmlub21pYWwsCiAgICBkYXRhID0gLgogICkKCm1hcF9kZnIoCiAgLnggPSBsaXN0KAogICAgImRpc3QyIiA9IHN0YXRzX2hlbGRvdXRfYWNjdXJhY3lfZGlzdDIsCiAgICAiZGlzdDMiID0gc3RhdHNfaGVsZG91dF9hY2N1cmFjeV9kaXN0MywKICAgICJkaXN0NCIgPSBzdGF0c19oZWxkb3V0X2FjY3VyYWN5X2Rpc3Q0CiAgKSwKICAuZiA9IH50aWR5KC54KSAlPiUgc2VsZWN0KC1jb21wb25lbnQpLAogIC5pZCA9ICJyZWZfY2F0IgopICU+JQogIGthYmxlX2N1c3RvbSgKICAgICJIZWxkLW91dCB0cmlhbHM6IFNSIG1vZGVsIGFjY3VyYWN5IiwKICAgIGdyb3VwaW5nX3ZhciA9IHJlZl9jYXQKICApCmBgYAoKSW4gdGhlIHBsb3QgYmVsb3csIHdlIGNhbiBzZWUgdGhhdCB0aGVyZSdzIHF1aXRlIGEgYml0IG9mIHZhcmlhYmlsaXR5IGFjcm9zcyBzdWJqZWN0cyAoYmxhY2sgbGluZXMgYW5kIGRhdGFwb2ludHMpLCBidXQgYWxzbyB0aGF0IGF0IHRoZSBncm91cCBsZXZlbCwgdGhlIFNSIGFjaGlldmVzIGFib3ZlLWNoYW5jZSBhY2N1cmFjeSBmb3IgdGhlIGhlbGQtb3V0IGRpc3RhbmNlLTMgYW5kIGRpc3RhbmNlLTQgdHJpYWxzIChyZWQpLgoKYGBge3IgcGxvdC1oZWxkb3V0LWFjY3VyYWN5fQpwcmVkaWN0X2hlbGRvdXRfYWNjdXJhY3kgPC0gdGliYmxlKAogIHNob3J0ZXN0X3BhdGggPSBmYWN0b3IoMjo0KSwgc3ViX2lkID0gTkEsIHN0dWR5ID0gTkEKKSAlPiUKICBwcmVkaWN0X2dsbW1UTUIoc3RhdHNfaGVsZG91dF9hY2N1cmFjeV9kaXN0MikKCnBsb3RfaGVsZG91dF9hY2N1cmFjeSA8LSBoZWxkb3V0X2xpa2VsaWhvb2RzICU+JQogIGZpbHRlcihtZWFzdXJlbWVudF9pZCAlaW4lIGMoIkQxIiwgIkQyIikpICU+JQogIGZpbHRlcighaXMubmEoc3JfcHJlZmVycykpICU+JQogIGdyb3VwX2J5KHN0dWR5LCBzdWJfaWQsIHNob3J0ZXN0X3BhdGgpICU+JQogIHN1bW1hcmlzZSgKICAgIHBfc3ViX2Nob29zZXNfc3JfcHJlZmVyZW5jZSA9IG1lYW4oc3ViX2Nob2ljZSA9PSBzcl9wcmVmZXJzKSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApICU+JQogIGdncGxvdChhZXMoeD1zaG9ydGVzdF9wYXRoLCB5PXBfc3ViX2Nob29zZXNfc3JfcHJlZmVyZW5jZSkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IGludGVyYWN0aW9uKHN0dWR5LCBzdWJfaWQpKSwgYWxwaGEgPSAwLjEpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArCiAgZ2VvbV9wb2ludHJhbmdlKAogICAgYWVzKHggPSBzaG9ydGVzdF9wYXRoLCB5ID0gZml0LCB5bWluID0gZml0IC0gc2UuZml0LCB5bWF4ID0gZml0ICsgc2UuZml0KSwKICAgIGRhdGEgPSBwcmVkaWN0X2hlbGRvdXRfYWNjdXJhY3ksIGluaGVyaXQuYWVzID0gRkFMU0UsCiAgICAjIGZhdHRlbiA9IDEsIHNpemUgPSAxLAogICAgbGluZXdpZHRoID0gMSwgY29sb3IgPSAicmVkIgogICkgKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJTaG9ydGVzdCBwYXRoIGRpc3RhbmNlIikgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIG5hbWUgPSAicChIdW1hbiBjaG9vc2VzIFNSLXByZWZlcnJlZCBTb3VyY2UpIiwKICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwKICAgIGJyZWFrcyA9IHNlcSgwLCAxLCAuMjUpLAogICAgZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAuMSwgMC4xNSkpCiAgKSArCiAgZ2d0aXRsZSgiSGVsZC1vdXQgdHJpYWxzOiBTUiBtb2RlbCBhY2N1cmFjeSIpCgpwbG90X2hlbGRvdXRfYWNjdXJhY3kKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJoZWxkb3V0X2FjY3VyYWN5LnBkZiIpLAogICAgcGxvdCA9IHBsb3RfaGVsZG91dF9hY2N1cmFjeSwKICAgIHdpZHRoID0gNCwgaGVpZ2h0ID0gNSwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKCiMgTWNGYWRkZW4ncyBwc2V1ZG8gUi1zcXVhcmVkCgpGaW5hbGx5LCBpdCBjYW4gYmUgdXNlZnVsIHRvIGdldCBhIHNlbnNlIGZvciBlYWNoIG1vZGVsJ3MgImdvb2RuZXNzLW9mLWZpdCIgYnkgY29tcHV0aW5nIHRoZSByYXRpbyBvZiBpdHMgbGlrZWxpaG9vZCB0byB0aGUgbGlrZWxpaG9vZCBvZiBhIG51bGwgbW9kZWwgKGkuZS4sIE1jRmFkZGVuJ3MgUi1zcXVhcmVkKS4gSGVyZSwgdGhlIG51bGwgbW9kZWwgaXMgYW4gYWdlbnQgdGhhdCBjaG9vc2VzIGNvbXBsZXRlbHkgYXQgcmFuZG9tIG9uIGV2ZXJ5IHRyaWFsLgoKYGBge3IgY2FsYy1tY2ZhZGRlbi1yMn0KbWNmYWRkZW5fcjIgPC0gYWljYyAlPiUKICBmaWx0ZXIobW9kZWwgIT0gInNyX2RlbHRhX3J1bGUiKSAlPiUKICBtdXRhdGUobG9nbGlrID0gLW5lZ19sb2dsaWspICU+JQogIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgbW9kZWwsIG1vZGVsX2xvZ2xpayA9IGxvZ2xpaykgJT4lCiAgbXV0YXRlKAogICAgIyBOdWxsIG1vZGVsIGlzIGNoYW5jZS1sZXZlbCBjaG9pY2Ugb24gZXZlcnkgdHJpYWwsIHNvIHRoaXMgZW5kcyB1cCBiZWluZwogICAgIyBleGFjdGx5IGVxdWFsIHRvIHRoZSB1c3VhbCBmb3JtdWxhdGlvbgogICAgbnVsbF9sb2dsaWsgPSBuYXZfdHJpYWxzICU+JQogICAgICBmaWx0ZXIodHdvX2NvcnJlY3Rfb3B0aW9ucyA9PSBGQUxTRSkgJT4lCiAgICAgIG5yb3coKSAlPiUKICAgICAgey4gKiBsb2coMC41KX0KICApICU+JQogIG11dGF0ZShtY2ZhZGRlbl9yMiA9IDEgLSAobW9kZWxfbG9nbGlrIC8gbnVsbF9sb2dsaWspKQoKbWNmYWRkZW5fcjIgJT4lCiAgZ3JvdXBfYnkoc3R1ZHksIG1lYXN1cmVtZW50X2lkLCBtb2RlbCkgJT4lCiAgc3VtbWFyaXNlKE1lYW4gPSBtZWFuKG1jZmFkZGVuX3IyKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IG1vZGVsLCB2YWx1ZXNfZnJvbSA9IE1lYW4pICU+JQogIGthYmxlX2N1c3RvbSgiTWVhbiBNY0ZhZGRlbidzIFIyIiwgZ3JvdXBpbmdfdmFyID0gc3R1ZHkpCgptY2ZhZGRlbl9yMiAlPiUKICBncm91cF9ieShzdHVkeSwgbWVhc3VyZW1lbnRfaWQsIG1vZGVsKSAlPiUKICBzdW1tYXJpc2UoTWVkaWFuID0gbWVkaWFuKG1jZmFkZGVuX3IyKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IG1vZGVsLCB2YWx1ZXNfZnJvbSA9IE1lZGlhbikgJT4lCiAga2FibGVfY3VzdG9tKCJNZWRpYW4gTWNGYWRkZW4ncyBSMiIsIGdyb3VwaW5nX3ZhciA9IHN0dWR5KQpgYGAKCldlIHNlZSBpbiB0aGUgcGxvdCB0aGF0LCBnZW5lcmFsbHksIGFsbCBtb2RlbHMgYXJlIGRvaW5nIGJldHRlciB0aGFuIHRoZSBudWxsIG1vZGVsLCBhbmQgYWxzbyB0aGF0IHRoZSBTUiBjb25zaXN0ZW50bHkgc2VlbXMgdG8gaGF2ZSB0aGUgYmVzdCBsaWtlbGlob29kIHJhdGlvLgoKYGBge3IgcGxvdC1tY2ZhZGRlbi1yMn0KcGxvdF9tY2ZhZGRlbl9yMiA8LSBtY2ZhZGRlbl9yMiAlPiUKICBnZ3Bsb3QoYWVzKHg9bWVhc3VyZW1lbnRfaWQsIHk9bWNmYWRkZW5fcjIsIGNvbG9yPW1vZGVsKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zdHVkeSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX3BvaW50KAogICAgYWxwaGEgPSAwLjEsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcmRvZGdlKAogICAgICBqaXR0ZXIud2lkdGggPSAwLjEsIGppdHRlci5oZWlnaHQgPSAwLCBkb2RnZS53aWR0aCA9IDAuNzUsIHNlZWQgPSAxCiAgICApLAogICAgc2hvdy5sZWdlbmQgPSBGQUxTRQogICkgKwogIHN0YXRfc3VtbWFyeSgKICAgIGdlb20gPSAiY3Jvc3NiYXIiLCBmdW4gPSBtZWRpYW4sIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjc1KQogICkgKwogIHNjYWxlX3hfZGlzY3JldGUoCiAgICBuYW1lID0gTlVMTCwKICAgIGxhYmVscyA9IGMoCiAgICAgICJEMSI9IkJlZm9yZVxucmVzdCIsCiAgICAgICJEMiI9IkFmdGVyXG5vdmVybmlnaHRcbnJlc3QiLAogICAgICAiRDFiIj0iQWZ0ZXJcbmF3YWtlXG5yZXN0IgogICAgKQogICkgKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gYnF1b3RlKH4iTWNGYWRkZW4ncyJ+Ul4yKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgIG5hbWUgPSBOVUxMLAogICAgbGFiZWxzID0gYygKICAgICAgImJmc19iYWNrd2FyZCIgPSAiQkZTLWJhY2t3YXJkIiwKICAgICAgImJmc19mb3J3YXJkIiA9ICJCRlMtZm9yd2FyZCIsCiAgICAgICJpZGVhbF9vYnMiID0gIklkZWFsIG9ic2VydmVyIiwKICAgICAgInNyX2FuYWx5dGljIiA9ICJTdWNjZXNzb3IgUmVwcmVzZW50YXRpb24iCiAgICApLAogICAgdmFsdWVzID0gYygKICAgICAgImJmc19iYWNrd2FyZCIgPSAiI2E2ZGJhMCIsCiAgICAgICJiZnNfZm9yd2FyZCIgPSAiIzVhYWU2MSIsCiAgICAgICJpZGVhbF9vYnMiID0gIiMxYjc4MzciLAogICAgICAic3JfYW5hbHl0aWMiID0gIiNhZjhkYzMiCiAgICApCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsKICBnZ3RpdGxlKCJNb2RlbCBnb29kbmVzcy1vZi1maXQiKQoKcGxvdF9tY2ZhZGRlbl9yMgoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJvdXRwdXRzIiwgd29ya2Zsb3dfbmFtZSwgIm1jZmFkZGVuX3IyLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfbWNmYWRkZW5fcjIsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDQsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKfQpgYGAKCgojIEZpZ3VyZXMgZm9yIHN1cHBsZW1lbnQKCmBgYHtyIHBsb3QtbW9kZWwtY29tcGFyaXNvbi1mb3Itc3VwcH0KcGxvdF9tb2RlbF9jb21wYXJpc29uX2Zvcl9zdXBwIDwtIHdyYXBfcGxvdHMoCiAgcGxvdF9ha2Fpa2VfZ3JvdXAsCiAgcGxvdF9iZXN0X2ZpdHRpbmdfbW9kZWxfcHJvcCwKICBwbG90X3B4cCwKICBndWlkZXMgPSAiY29sbGVjdCIsIG5yb3cgPSAxCikgKwogIHBsb3RfYW5ub3RhdGlvbigKICAgIHRpdGxlID0gIk1vZGVsIGNvbXBhcmlzb24iLAogICAgdGFnX2xldmVscyA9ICJBIiwgdGFnX3N1ZmZpeCA9ICIuIiwKICAgIHRoZW1lID0gdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCiAgKSAmCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgpwbG90X21vZGVsX2NvbXBhcmlzb25fZm9yX3N1cHAKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgiZmlndXJlcyIsICJzdXBwX21vZGVsX2NvbXBhcmlzb24ucGRmIiksCiAgICBwbG90ID0gcGxvdF9tb2RlbF9jb21wYXJpc29uX2Zvcl9zdXBwLAogICAgd2lkdGggPSAxNiwgaGVpZ2h0ID0gNCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKYGBge3IgcGxvdC1wcGMtZm9yLXN1cHB9CnBsb3RfcHBjX2Zvcl9zdXBwIDwtIHdyYXBfcGxvdHMoCiAgcGxvdF9wcGNfZGF5MiArIGdndGl0bGUoIkFmdGVyIG92ZXJuaWdodCByZXN0IChTdHVkaWVzIDItMykiKSwKICBwbG90X3BwY19kYXkxYiArIGdndGl0bGUoIkFmdGVyIGF3YWtlIHJlc3QgKFN0dWR5IDMpIiksCiAgbmNvbCA9IDEKKSArCiAgcGxvdF9hbm5vdGF0aW9uKAogICAgdGl0bGUgPSAiUG9zdGVyaW9yIHByZWRpY3RpdmUgY2hlY2siLAogICAgdGFnX2xldmVscyA9ICJBIiwgdGFnX3N1ZmZpeCA9ICIuIiwKICAgIHRoZW1lID0gdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCiAgKSAmCiAgc2NhbGVfeV9jb250aW51b3VzKAogICAgbmFtZSA9ICJBY2N1cmFjeSIsIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwKICAgIGxpbWl0cyA9IGMoLjI1LCAxKSwKICAgIGJyZWFrcyA9IHNlcSguMjUsIDEsIC4yNSkKICApCgpwbG90X3BwY19mb3Jfc3VwcAoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJmaWd1cmVzIiwgInN1cHBfcHBjLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfcHBjX2Zvcl9zdXBwLAogICAgd2lkdGggPSA4LCBoZWlnaHQgPSA2LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCn0KYGBgCgpUaGVyZSBhcmUgc29tZSBwbG90cyB0aGF0IHdlIHdhbnQgdG8gdXNlIGFzLWlzLCBzbyB3ZSdsbCBzYXZlIGEgcmVkdW5kYW50IGNvcHkgaW4gdGhlIGBmaWd1cmVzYCBmb2xkZXIuCgpgYGB7ciBzYXZlLXJlZHVuZGFudC1jb3BpZXN9CmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgiZmlndXJlcyIsICJzdXBwX3BhcmFtX2VzdGltYXRlcy5wZGYiKSwKICAgIHBsb3QgPSBwbG90X3BhcmFtc19hbGwsCiAgICB3aWR0aCA9IDEyLCBoZWlnaHQgPSA2LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCiAgCiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJmaWd1cmVzIiwgInN1cHBfYWthaWtlX3dlaWdodHNfcGVyX3N1YmplY3QucGRmIiksCiAgICBwbG90ID0gcGxvdF9ha2Fpa2VfaW5kaXZpZHVhbCwKICAgIHdpZHRoID0gOCwgaGVpZ2h0ID0gMTAsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKICAKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoImZpZ3VyZXMiLCAic3VwcF9oZWxkb3V0X2xpa2VsaWhvb2RzLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfaGVsZG91dF9saWtlbGlob29kcywKICAgIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQogIAogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgiZmlndXJlcyIsICJzdXBwX2hlbGRvdXRfYWNjdXJhY3kucGRmIiksCiAgICBwbG90ID0gcGxvdF9oZWxkb3V0X2FjY3VyYWN5LAogICAgd2lkdGggPSA0LCBoZWlnaHQgPSA1LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCiAgCiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJmaWd1cmVzIiwgInN1cHBfbWNmYWRkZW5fcjIucGRmIiksCiAgICBwbG90ID0gcGxvdF9tY2ZhZGRlbl9yMiwKICAgIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoK